aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/components
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app/components')
-rw-r--r--ui/app/components/account-export.js138
-rw-r--r--ui/app/components/account-menu/index.js248
-rw-r--r--ui/app/components/app/account-dropdowns.js (renamed from ui/app/components/account-dropdowns.js)19
-rw-r--r--ui/app/components/app/account-menu/account-menu.component.js340
-rw-r--r--ui/app/components/app/account-menu/account-menu.container.js62
-rw-r--r--ui/app/components/app/account-menu/index.js1
-rw-r--r--ui/app/components/app/account-menu/index.scss177
-rw-r--r--ui/app/components/app/account-panel.js (renamed from ui/app/components/account-panel.js)6
-rw-r--r--ui/app/components/app/add-token-button/add-token-button.component.js (renamed from ui/app/components/add-token-button/add-token-button.component.js)0
-rw-r--r--ui/app/components/app/add-token-button/index.js (renamed from ui/app/components/add-token-button/index.js)0
-rw-r--r--ui/app/components/app/add-token-button/index.scss (renamed from ui/app/components/add-token-button/index.scss)0
-rw-r--r--ui/app/components/app/app-header/app-header.component.js (renamed from ui/app/components/app-header/app-header.component.js)105
-rw-r--r--ui/app/components/app/app-header/app-header.container.js (renamed from ui/app/components/app-header/app-header.container.js)4
-rw-r--r--ui/app/components/app/app-header/index.js (renamed from ui/app/components/app-header/index.js)0
-rw-r--r--ui/app/components/app/app-header/index.scss (renamed from ui/app/components/app-header/index.scss)0
-rw-r--r--ui/app/components/app/bn-as-decimal-input.js (renamed from ui/app/components/bn-as-decimal-input.js)0
-rw-r--r--ui/app/components/app/coinbase-form.js (renamed from ui/app/components/coinbase-form.js)2
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js (renamed from ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js)2
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-detail-row/index.js (renamed from ui/app/components/confirm-page-container/confirm-detail-row/index.js)0
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-detail-row/index.scss (renamed from ui/app/components/confirm-page-container/confirm-detail-row/index.scss)4
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js (renamed from ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js)0
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js (renamed from ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js)6
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js (renamed from ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js)2
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.js (renamed from ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.js)0
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss (renamed from ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss)0
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/confirm-page-container-warning.component.js (renamed from ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/confirm-page-container-warning.component.js)0
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.js (renamed from ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.js)0
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.scss (renamed from ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.scss)0
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-page-container-content/index.js (renamed from ui/app/components/confirm-page-container/confirm-page-container-content/index.js)0
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-page-container-content/index.scss (renamed from ui/app/components/confirm-page-container/confirm-page-container-content/index.scss)8
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js (renamed from ui/app/components/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js)2
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-page-container-header/index.js (renamed from ui/app/components/confirm-page-container/confirm-page-container-header/index.js)0
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-page-container-header/index.scss (renamed from ui/app/components/confirm-page-container/confirm-page-container-header/index.scss)2
-rwxr-xr-xui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js69
-rwxr-xr-xui/app/components/app/confirm-page-container/confirm-page-container-navigation/index.js1
-rwxr-xr-xui/app/components/app/confirm-page-container/confirm-page-container-navigation/index.scss54
-rw-r--r--ui/app/components/app/confirm-page-container/confirm-page-container.component.js (renamed from ui/app/components/confirm-page-container/confirm-page-container.component.js)39
-rw-r--r--ui/app/components/app/confirm-page-container/index.js (renamed from ui/app/components/confirm-page-container/index.js)2
-rw-r--r--ui/app/components/app/confirm-page-container/index.scss7
-rw-r--r--ui/app/components/app/copyable.js (renamed from ui/app/components/copyable.js)2
-rw-r--r--ui/app/components/app/customize-gas-modal/gas-modal-card.js (renamed from ui/app/components/customize-gas-modal/gas-modal-card.js)0
-rw-r--r--ui/app/components/app/customize-gas-modal/gas-slider.js (renamed from ui/app/components/customize-gas-modal/gas-slider.js)0
-rw-r--r--ui/app/components/app/customize-gas-modal/index.js (renamed from ui/app/components/customize-gas-modal/index.js)32
-rw-r--r--ui/app/components/app/dropdowns/account-details-dropdown.js (renamed from ui/app/components/dropdowns/account-details-dropdown.js)28
-rw-r--r--ui/app/components/app/dropdowns/components/account-dropdowns.js (renamed from ui/app/components/dropdowns/components/account-dropdowns.js)10
-rw-r--r--ui/app/components/app/dropdowns/components/dropdown.js (renamed from ui/app/components/dropdowns/components/dropdown.js)0
-rw-r--r--ui/app/components/app/dropdowns/components/menu.js (renamed from ui/app/components/dropdowns/components/menu.js)0
-rw-r--r--ui/app/components/app/dropdowns/components/network-dropdown-icon.js47
-rw-r--r--ui/app/components/app/dropdowns/index.js (renamed from ui/app/components/dropdowns/index.js)0
-rw-r--r--ui/app/components/app/dropdowns/network-dropdown.js (renamed from ui/app/components/dropdowns/network-dropdown.js)37
-rw-r--r--ui/app/components/app/dropdowns/simple-dropdown.js (renamed from ui/app/components/dropdowns/simple-dropdown.js)0
-rw-r--r--ui/app/components/app/dropdowns/tests/dropdown.test.js (renamed from ui/app/components/dropdowns/tests/dropdown.test.js)0
-rw-r--r--ui/app/components/app/dropdowns/tests/menu.test.js (renamed from ui/app/components/dropdowns/tests/menu.test.js)0
-rw-r--r--ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js (renamed from ui/app/components/dropdowns/tests/network-dropdown-icon.test.js)0
-rw-r--r--ui/app/components/app/dropdowns/tests/network-dropdown.test.js (renamed from ui/app/components/dropdowns/tests/network-dropdown.test.js)2
-rw-r--r--ui/app/components/app/dropdowns/token-menu-dropdown.js (renamed from ui/app/components/dropdowns/token-menu-dropdown.js)2
-rw-r--r--ui/app/components/app/ens-input.js (renamed from ui/app/components/ens-input.js)4
-rw-r--r--ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js156
-rw-r--r--ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js38
-rw-r--r--ui/app/components/app/gas-customization/advanced-gas-inputs/index.js1
-rw-r--r--ui/app/components/app/gas-customization/advanced-gas-inputs/index.scss133
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js226
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.js1
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss207
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js365
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.js1
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.scss17
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/tests/time-remaining-component.test.js30
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.component.js33
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.utils.js11
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js35
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/index.js1
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/index.scss28
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js82
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js186
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js295
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/index.js1
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/index.scss146
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js274
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js431
-rw-r--r--ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js89
-rw-r--r--ui/app/components/app/gas-customization/gas-price-button-group/index.js1
-rw-r--r--ui/app/components/app/gas-customization/gas-price-button-group/index.scss238
-rw-r--r--ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js233
-rw-r--r--ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.component.js108
-rw-r--r--ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js354
-rw-r--r--ui/app/components/app/gas-customization/gas-price-chart/index.js1
-rw-r--r--ui/app/components/app/gas-customization/gas-price-chart/index.scss132
-rw-r--r--ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js218
-rw-r--r--ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js48
-rw-r--r--ui/app/components/app/gas-customization/gas-slider/index.js1
-rw-r--r--ui/app/components/app/gas-customization/gas-slider/index.scss54
-rw-r--r--ui/app/components/app/gas-customization/gas.selectors.js14
-rw-r--r--ui/app/components/app/gas-customization/index.scss7
-rw-r--r--ui/app/components/app/index.scss81
-rw-r--r--ui/app/components/app/info-box/index.js (renamed from ui/app/components/info-box/index.js)0
-rw-r--r--ui/app/components/app/info-box/index.scss (renamed from ui/app/components/info-box/index.scss)0
-rw-r--r--ui/app/components/app/info-box/info-box.component.js (renamed from ui/app/components/info-box/info-box.component.js)0
-rw-r--r--ui/app/components/app/input-number.js (renamed from ui/app/components/input-number.js)2
-rw-r--r--ui/app/components/app/loading-network-screen/index.js1
-rw-r--r--ui/app/components/app/loading-network-screen/loading-network-screen.component.js138
-rw-r--r--ui/app/components/app/loading-network-screen/loading-network-screen.container.js41
-rw-r--r--ui/app/components/app/menu-bar/index.js (renamed from ui/app/components/menu-bar/index.js)0
-rw-r--r--ui/app/components/app/menu-bar/index.scss (renamed from ui/app/components/menu-bar/index.scss)0
-rw-r--r--ui/app/components/app/menu-bar/menu-bar.component.js79
-rw-r--r--ui/app/components/app/menu-bar/menu-bar.container.js (renamed from ui/app/components/menu-bar/menu-bar.container.js)8
-rw-r--r--ui/app/components/app/menu-droppo.js (renamed from ui/app/components/menu-droppo.js)0
-rw-r--r--ui/app/components/app/modal/index.js (renamed from ui/app/components/modal/index.js)0
-rw-r--r--ui/app/components/app/modal/index.scss (renamed from ui/app/components/modal/index.scss)2
-rw-r--r--ui/app/components/app/modal/modal-content/index.js (renamed from ui/app/components/modal/modal-content/index.js)0
-rw-r--r--ui/app/components/app/modal/modal-content/index.scss (renamed from ui/app/components/modal/modal-content/index.scss)0
-rw-r--r--ui/app/components/app/modal/modal-content/modal-content.component.js (renamed from ui/app/components/modal/modal-content/modal-content.component.js)0
-rw-r--r--ui/app/components/app/modal/modal-content/tests/modal-content.component.test.js (renamed from ui/app/components/modal/modal-content/tests/modal-content.component.test.js)0
-rw-r--r--ui/app/components/app/modal/modal.component.js (renamed from ui/app/components/modal/modal.component.js)5
-rw-r--r--ui/app/components/app/modal/tests/modal.component.test.js (renamed from ui/app/components/modal/tests/modal.component.test.js)34
-rw-r--r--ui/app/components/app/modals/account-details-modal.js (renamed from ui/app/components/modals/account-details-modal.js)13
-rw-r--r--ui/app/components/app/modals/account-modal-container.js (renamed from ui/app/components/modals/account-modal-container.js)6
-rw-r--r--ui/app/components/app/modals/buy-options-modal.js (renamed from ui/app/components/modals/buy-options-modal.js)4
-rw-r--r--ui/app/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js (renamed from ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js)2
-rw-r--r--ui/app/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/index.js (renamed from ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.js)0
-rw-r--r--ui/app/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/index.scss (renamed from ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.scss)0
-rw-r--r--ui/app/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js (renamed from ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js)0
-rw-r--r--ui/app/components/app/modals/cancel-transaction/cancel-transaction.component.js (renamed from ui/app/components/modals/cancel-transaction/cancel-transaction.component.js)12
-rw-r--r--ui/app/components/app/modals/cancel-transaction/cancel-transaction.container.js (renamed from ui/app/components/modals/cancel-transaction/cancel-transaction.container.js)24
-rw-r--r--ui/app/components/app/modals/cancel-transaction/index.js (renamed from ui/app/components/modals/cancel-transaction/index.js)0
-rw-r--r--ui/app/components/app/modals/cancel-transaction/index.scss (renamed from ui/app/components/modals/cancel-transaction/index.scss)4
-rw-r--r--ui/app/components/app/modals/cancel-transaction/tests/cancel-transaction.component.test.js (renamed from ui/app/components/modals/cancel-transaction/tests/cancel-transaction.component.test.js)1
-rw-r--r--ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js (renamed from ui/app/components/modals/clear-approved-origins/clear-approved-origins.component.js)0
-rw-r--r--ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.container.js (renamed from ui/app/components/modals/clear-approved-origins/clear-approved-origins.container.js)4
-rw-r--r--ui/app/components/app/modals/clear-approved-origins/index.js (renamed from ui/app/components/modals/clear-approved-origins/index.js)0
-rw-r--r--ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js (renamed from ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js)6
-rw-r--r--ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.container.js (renamed from ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js)4
-rw-r--r--ui/app/components/app/modals/confirm-remove-account/index.js (renamed from ui/app/components/modals/confirm-remove-account/index.js)0
-rw-r--r--ui/app/components/app/modals/confirm-remove-account/index.scss (renamed from ui/app/components/modals/confirm-remove-account/index.scss)0
-rw-r--r--ui/app/components/app/modals/confirm-reset-account/confirm-reset-account.component.js (renamed from ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js)0
-rw-r--r--ui/app/components/app/modals/confirm-reset-account/confirm-reset-account.container.js (renamed from ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js)4
-rw-r--r--ui/app/components/app/modals/confirm-reset-account/index.js (renamed from ui/app/components/modals/confirm-reset-account/index.js)0
-rw-r--r--ui/app/components/app/modals/customize-gas/customize-gas.component.js (renamed from ui/app/components/modals/customize-gas/customize-gas.component.js)27
-rw-r--r--ui/app/components/app/modals/customize-gas/customize-gas.container.js (renamed from ui/app/components/modals/customize-gas/customize-gas.container.js)2
-rw-r--r--ui/app/components/app/modals/customize-gas/customize-gas.util.js (renamed from ui/app/components/modals/customize-gas/customize-gas.util.js)2
-rw-r--r--ui/app/components/app/modals/customize-gas/index.js (renamed from ui/app/components/modals/customize-gas/index.js)0
-rw-r--r--ui/app/components/app/modals/customize-gas/index.scss (renamed from ui/app/components/modals/customize-gas/index.scss)0
-rw-r--r--ui/app/components/app/modals/deposit-ether-modal.js (renamed from ui/app/components/modals/deposit-ether-modal.js)22
-rw-r--r--ui/app/components/app/modals/edit-account-name-modal.js (renamed from ui/app/components/modals/edit-account-name-modal.js)4
-rw-r--r--ui/app/components/app/modals/export-private-key-modal.js (renamed from ui/app/components/modals/export-private-key-modal.js)10
-rw-r--r--ui/app/components/app/modals/hide-token-confirmation-modal.js (renamed from ui/app/components/modals/hide-token-confirmation-modal.js)4
-rw-r--r--ui/app/components/app/modals/index.js (renamed from ui/app/components/modals/index.js)0
-rw-r--r--ui/app/components/app/modals/index.scss11
-rw-r--r--ui/app/components/app/modals/loading-network-error/index.js1
-rw-r--r--ui/app/components/app/modals/loading-network-error/loading-network-error.component.js (renamed from ui/app/components/modals/welcome-beta/welcome-beta.component.js)13
-rw-r--r--ui/app/components/app/modals/loading-network-error/loading-network-error.container.js4
-rw-r--r--ui/app/components/app/modals/metametrics-opt-in-modal/index.js1
-rw-r--r--ui/app/components/app/modals/metametrics-opt-in-modal/index.scss30
-rw-r--r--ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js141
-rw-r--r--ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js24
-rw-r--r--ui/app/components/app/modals/modal.js (renamed from ui/app/components/modals/modal.js)110
-rw-r--r--ui/app/components/app/modals/new-account-modal.js (renamed from ui/app/components/modals/new-account-modal.js)2
-rw-r--r--ui/app/components/app/modals/notification-modal.js (renamed from ui/app/components/modals/notification-modal.js)2
-rw-r--r--ui/app/components/app/modals/qr-scanner/index.js (renamed from ui/app/components/modals/qr-scanner/index.js)0
-rw-r--r--ui/app/components/app/modals/qr-scanner/index.scss (renamed from ui/app/components/modals/qr-scanner/index.scss)0
-rw-r--r--ui/app/components/app/modals/qr-scanner/qr-scanner.component.js (renamed from ui/app/components/modals/qr-scanner/qr-scanner.component.js)6
-rw-r--r--ui/app/components/app/modals/qr-scanner/qr-scanner.container.js (renamed from ui/app/components/modals/qr-scanner/qr-scanner.container.js)4
-rw-r--r--ui/app/components/app/modals/reject-transactions/index.js (renamed from ui/app/components/modals/reject-transactions/index.js)0
-rw-r--r--ui/app/components/app/modals/reject-transactions/index.scss (renamed from ui/app/components/modals/reject-transactions/index.scss)0
-rw-r--r--ui/app/components/app/modals/reject-transactions/reject-transactions.component.js (renamed from ui/app/components/modals/reject-transactions/reject-transactions.component.js)0
-rw-r--r--ui/app/components/app/modals/reject-transactions/reject-transactions.container.js (renamed from ui/app/components/modals/reject-transactions/reject-transactions.container.js)2
-rw-r--r--ui/app/components/app/modals/shapeshift-deposit-tx-modal.js (renamed from ui/app/components/modals/shapeshift-deposit-tx-modal.js)4
-rw-r--r--ui/app/components/app/modals/transaction-confirmed/index.js (renamed from ui/app/components/modals/transaction-confirmed/index.js)0
-rw-r--r--ui/app/components/app/modals/transaction-confirmed/index.scss (renamed from ui/app/components/modals/transaction-confirmed/index.scss)0
-rw-r--r--ui/app/components/app/modals/transaction-confirmed/transaction-confirmed.component.js (renamed from ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js)0
-rw-r--r--ui/app/components/app/modals/transaction-confirmed/transaction-confirmed.container.js (renamed from ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js)2
-rw-r--r--ui/app/components/app/network-display/index.js (renamed from ui/app/components/network-display/index.js)0
-rw-r--r--ui/app/components/app/network-display/index.scss (renamed from ui/app/components/network-display/index.scss)0
-rw-r--r--ui/app/components/app/network-display/network-display.component.js (renamed from ui/app/components/network-display/network-display.component.js)2
-rw-r--r--ui/app/components/app/network-display/network-display.container.js (renamed from ui/app/components/network-display/network-display.container.js)0
-rw-r--r--ui/app/components/app/network.js (renamed from ui/app/components/network.js)53
-rw-r--r--ui/app/components/app/notice.js (renamed from ui/app/components/notice.js)0
-rw-r--r--ui/app/components/app/provider-page-container/index.js (renamed from ui/app/components/provider-page-container/index.js)0
-rw-r--r--ui/app/components/app/provider-page-container/index.scss (renamed from ui/app/components/provider-page-container/index.scss)0
-rw-r--r--ui/app/components/app/provider-page-container/provider-page-container-content/index.js (renamed from ui/app/components/provider-page-container/provider-page-container-content/index.js)0
-rw-r--r--ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js (renamed from ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.component.js)2
-rw-r--r--ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js (renamed from ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.container.js)2
-rw-r--r--ui/app/components/app/provider-page-container/provider-page-container-header/index.js (renamed from ui/app/components/provider-page-container/provider-page-container-header/index.js)0
-rw-r--r--ui/app/components/app/provider-page-container/provider-page-container-header/provider-page-container-header.component.js (renamed from ui/app/components/provider-page-container/provider-page-container-header/provider-page-container-header.component.js)0
-rw-r--r--ui/app/components/app/provider-page-container/provider-page-container.component.js (renamed from ui/app/components/provider-page-container/provider-page-container.component.js)38
-rw-r--r--ui/app/components/app/selected-account/index.js (renamed from ui/app/components/selected-account/index.js)0
-rw-r--r--ui/app/components/app/selected-account/index.scss (renamed from ui/app/components/selected-account/index.scss)0
-rw-r--r--ui/app/components/app/selected-account/selected-account.component.js (renamed from ui/app/components/selected-account/selected-account.component.js)9
-rw-r--r--ui/app/components/app/selected-account/selected-account.container.js (renamed from ui/app/components/selected-account/selected-account.container.js)3
-rw-r--r--ui/app/components/app/selected-account/tests/selected-account-component.test.js (renamed from ui/app/components/selected-account/tests/selected-account-component.test.js)0
-rw-r--r--ui/app/components/app/send/README.md (renamed from ui/app/components/send/README.md)0
-rw-r--r--ui/app/components/app/send/account-list-item/account-list-item-README.md (renamed from ui/app/components/send/account-list-item/account-list-item-README.md)0
-rw-r--r--ui/app/components/app/send/account-list-item/account-list-item.component.js108
-rw-r--r--ui/app/components/app/send/account-list-item/account-list-item.container.js (renamed from ui/app/components/send/account-list-item/account-list-item.container.js)10
-rw-r--r--ui/app/components/app/send/account-list-item/index.js (renamed from ui/app/components/send/account-list-item/index.js)0
-rw-r--r--ui/app/components/app/send/account-list-item/tests/account-list-item-component.test.js (renamed from ui/app/components/send/account-list-item/tests/account-list-item-component.test.js)19
-rw-r--r--ui/app/components/app/send/account-list-item/tests/account-list-item-container.test.js73
-rw-r--r--ui/app/components/app/send/index.js (renamed from ui/app/components/send/index.js)0
-rw-r--r--ui/app/components/app/send/send-content/index.js (renamed from ui/app/components/send/send-content/index.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/README.md (renamed from ui/app/components/send/send-content/send-amount-row/README.md)0
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js (renamed from ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js)11
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js (renamed from ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js)4
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js (renamed from ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js29
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/amount-max-button/index.js (renamed from ui/app/components/send/send-content/send-amount-row/amount-max-button/index.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js (renamed from ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js (renamed from ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js)4
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js (renamed from ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js (renamed from ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js)2
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/index.js (renamed from ui/app/components/send/send-content/send-amount-row/index.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/send-amount-row.component.js (renamed from ui/app/components/send/send-content/send-amount-row/send-amount-row.component.js)9
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/send-amount-row.container.js (renamed from ui/app/components/send/send-content/send-amount-row/send-amount-row.container.js)8
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/send-amount-row.scss (renamed from ui/app/components/send/send-content/send-amount-row/send-amount-row.scss)0
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/send-amount-row.selectors.js (renamed from ui/app/components/send/send-content/send-amount-row/send-amount-row.selectors.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-component.test.js (renamed from ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-component.test.js)1
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-container.test.js (renamed from ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-container.test.js)4
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js (renamed from ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-content.component.js (renamed from ui/app/components/send/send-content/send-content.component.js)12
-rw-r--r--ui/app/components/app/send/send-content/send-dropdown-list/index.js (renamed from ui/app/components/send/send-content/send-dropdown-list/index.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-dropdown-list/send-dropdown-list.component.js (renamed from ui/app/components/send/send-content/send-dropdown-list/send-dropdown-list.component.js)2
-rw-r--r--ui/app/components/app/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js (renamed from ui/app/components/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-from-row/index.js (renamed from ui/app/components/send/send-content/send-from-row/index.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-from-row/send-from-row.component.js27
-rw-r--r--ui/app/components/app/send/send-content/send-from-row/send-from-row.container.js11
-rw-r--r--ui/app/components/app/send/send-content/send-from-row/send-from-row.selectors.js (renamed from ui/app/components/send/send-content/send-from-row/send-from-row.selectors.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-component.test.js31
-rw-r--r--ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-container.test.js26
-rw-r--r--ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-selectors.test.js (renamed from ui/app/components/send/send-content/send-from-row/tests/send-from-row-selectors.test.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-gas-row/README.md (renamed from ui/app/components/send/send-content/send-gas-row/README.md)0
-rw-r--r--ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js (renamed from ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js)13
-rw-r--r--ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/index.js (renamed from ui/app/components/send/send-content/send-gas-row/gas-fee-display/index.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js (renamed from ui/app/components/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js)18
-rw-r--r--ui/app/components/app/send/send-content/send-gas-row/index.js (renamed from ui/app/components/send/send-content/send-gas-row/index.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-gas-row/send-gas-row.component.js131
-rw-r--r--ui/app/components/app/send/send-content/send-gas-row/send-gas-row.container.js118
-rw-r--r--ui/app/components/app/send/send-content/send-gas-row/send-gas-row.scss (renamed from ui/app/components/send/send-content/send-gas-row/send-gas-row.scss)0
-rw-r--r--ui/app/components/app/send/send-content/send-gas-row/send-gas-row.selectors.js (renamed from ui/app/components/send/send-content/send-gas-row/send-gas-row.selectors.js)5
-rw-r--r--ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-component.test.js (renamed from ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-component.test.js)44
-rw-r--r--ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-container.test.js200
-rw-r--r--ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js (renamed from ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js)13
-rw-r--r--ui/app/components/app/send/send-content/send-hex-data-row/index.js (renamed from ui/app/components/send/send-content/send-hex-data-row/index.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.component.js (renamed from ui/app/components/send/send-content/send-hex-data-row/send-hex-data-row.component.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.container.js (renamed from ui/app/components/send/send-content/send-hex-data-row/send-hex-data-row.container.js)2
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/index.js (renamed from ui/app/components/send/send-content/send-row-wrapper/index.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/index.js (renamed from ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/index.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md (renamed from ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md)0
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js (renamed from ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js (renamed from ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss (renamed from ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss)0
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js (renamed from ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js (renamed from ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/index.js1
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js27
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js12
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.scss (renamed from ui/app/components/page-container/tests/page-container.component.test.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js28
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js28
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper-README.md (renamed from ui/app/components/send/send-content/send-row-wrapper/send-row-wrapper-README.md)0
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.component.js (renamed from ui/app/components/send/send-content/send-row-wrapper/send-row-wrapper.component.js)17
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.scss (renamed from ui/app/components/send/send-content/send-row-wrapper/send-row-wrapper.scss)0
-rw-r--r--ui/app/components/app/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js (renamed from ui/app/components/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-to-row/index.js (renamed from ui/app/components/send/send-content/send-to-row/index.js)0
-rw-r--r--ui/app/components/app/send/send-content/send-to-row/send-to-row-README.md (renamed from ui/app/components/send/send-content/send-to-row/send-to-row-README.md)0
-rw-r--r--ui/app/components/app/send/send-content/send-to-row/send-to-row.component.js (renamed from ui/app/components/send/send-content/send-to-row/send-to-row.component.js)39
-rw-r--r--ui/app/components/app/send/send-content/send-to-row/send-to-row.container.js (renamed from ui/app/components/send/send-content/send-to-row/send-to-row.container.js)14
-rw-r--r--ui/app/components/app/send/send-content/send-to-row/send-to-row.selectors.js (renamed from ui/app/components/send/send-content/send-to-row/send-to-row.selectors.js)10
-rw-r--r--ui/app/components/app/send/send-content/send-to-row/send-to-row.utils.js36
-rw-r--r--ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-component.test.js (renamed from ui/app/components/send/send-content/send-to-row/tests/send-to-row-component.test.js)21
-rw-r--r--ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-container.test.js (renamed from ui/app/components/send/send-content/send-to-row/tests/send-to-row-container.test.js)23
-rw-r--r--ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-selectors.test.js (renamed from ui/app/components/send/send-content/send-to-row/tests/send-to-row-selectors.test.js)12
-rw-r--r--ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-utils.test.js107
-rw-r--r--ui/app/components/app/send/send-content/tests/send-content-component.test.js (renamed from ui/app/components/send/send-content/tests/send-content-component.test.js)2
-rw-r--r--ui/app/components/app/send/send-footer/README.md (renamed from ui/app/components/send/send-footer/README.md)0
-rw-r--r--ui/app/components/app/send/send-footer/index.js (renamed from ui/app/components/send/send-footer/index.js)0
-rw-r--r--ui/app/components/app/send/send-footer/send-footer.component.js (renamed from ui/app/components/send/send-footer/send-footer.component.js)45
-rw-r--r--ui/app/components/app/send/send-footer/send-footer.container.js (renamed from ui/app/components/send/send-footer/send-footer.container.js)4
-rw-r--r--ui/app/components/app/send/send-footer/send-footer.scss (renamed from ui/app/components/send/send-footer/send-footer.scss)0
-rw-r--r--ui/app/components/app/send/send-footer/send-footer.selectors.js (renamed from ui/app/components/send/send-footer/send-footer.selectors.js)0
-rw-r--r--ui/app/components/app/send/send-footer/send-footer.utils.js (renamed from ui/app/components/send/send-footer/send-footer.utils.js)0
-rw-r--r--ui/app/components/app/send/send-footer/tests/send-footer-component.test.js (renamed from ui/app/components/send/send-footer/tests/send-footer-component.test.js)9
-rw-r--r--ui/app/components/app/send/send-footer/tests/send-footer-container.test.js (renamed from ui/app/components/send/send-footer/tests/send-footer-container.test.js)12
-rw-r--r--ui/app/components/app/send/send-footer/tests/send-footer-selectors.test.js (renamed from ui/app/components/send/send-footer/tests/send-footer-selectors.test.js)0
-rw-r--r--ui/app/components/app/send/send-footer/tests/send-footer-utils.test.js (renamed from ui/app/components/send/send-footer/tests/send-footer-utils.test.js)0
-rw-r--r--ui/app/components/app/send/send-header/README.md (renamed from ui/app/components/send/send-header/README.md)0
-rw-r--r--ui/app/components/app/send/send-header/index.js (renamed from ui/app/components/send/send-header/index.js)0
-rw-r--r--ui/app/components/app/send/send-header/send-header.component.js (renamed from ui/app/components/send/send-header/send-header.component.js)4
-rw-r--r--ui/app/components/app/send/send-header/send-header.container.js (renamed from ui/app/components/send/send-header/send-header.container.js)2
-rw-r--r--ui/app/components/app/send/send-header/send-header.selectors.js (renamed from ui/app/components/send/send-header/send-header.selectors.js)0
-rw-r--r--ui/app/components/app/send/send-header/tests/send-header-component.test.js (renamed from ui/app/components/send/send-header/tests/send-header-component.test.js)4
-rw-r--r--ui/app/components/app/send/send-header/tests/send-header-container.test.js (renamed from ui/app/components/send/send-header/tests/send-header-container.test.js)2
-rw-r--r--ui/app/components/app/send/send-header/tests/send-header-selectors.test.js (renamed from ui/app/components/send/send-header/tests/send-header-selectors.test.js)0
-rw-r--r--ui/app/components/app/send/send.component.js (renamed from ui/app/components/send/send.component.js)28
-rw-r--r--ui/app/components/app/send/send.constants.js (renamed from ui/app/components/send/send.constants.js)6
-rw-r--r--ui/app/components/app/send/send.container.js (renamed from ui/app/components/send/send.container.js)14
-rw-r--r--ui/app/components/app/send/send.scss (renamed from ui/app/components/send/send.scss)0
-rw-r--r--ui/app/components/app/send/send.selectors.js (renamed from ui/app/components/send/send.selectors.js)50
-rw-r--r--ui/app/components/app/send/send.utils.js (renamed from ui/app/components/send/send.utils.js)16
-rw-r--r--ui/app/components/app/send/tests/send-component.test.js (renamed from ui/app/components/send/tests/send-component.test.js)52
-rw-r--r--ui/app/components/app/send/tests/send-container.test.js (renamed from ui/app/components/send/tests/send-container.test.js)14
-rw-r--r--ui/app/components/app/send/tests/send-selectors-test-data.js (renamed from ui/app/components/send/tests/send-selectors-test-data.js)3
-rw-r--r--ui/app/components/app/send/tests/send-selectors.test.js (renamed from ui/app/components/send/tests/send-selectors.test.js)2
-rw-r--r--ui/app/components/app/send/tests/send-utils.test.js (renamed from ui/app/components/send/tests/send-utils.test.js)12
-rw-r--r--ui/app/components/app/send/to-autocomplete.component.js (renamed from ui/app/components/send/to-autocomplete.component.js)2
-rw-r--r--ui/app/components/app/send/to-autocomplete/index.js (renamed from ui/app/components/send/to-autocomplete/index.js)0
-rw-r--r--ui/app/components/app/send/to-autocomplete/to-autocomplete.js (renamed from ui/app/components/send/to-autocomplete/to-autocomplete.js)4
-rw-r--r--ui/app/components/app/shapeshift-form.js (renamed from ui/app/components/shapeshift-form.js)8
-rw-r--r--ui/app/components/app/shift-list-item.js (renamed from ui/app/components/shift-list-item.js)15
-rw-r--r--ui/app/components/app/sidebars/index.js (renamed from ui/app/components/sidebars/index.js)0
-rw-r--r--ui/app/components/app/sidebars/index.scss (renamed from ui/app/components/sidebars/index.scss)9
-rw-r--r--ui/app/components/app/sidebars/sidebar-content.scss112
-rw-r--r--ui/app/components/app/sidebars/sidebar.component.js69
-rw-r--r--ui/app/components/app/sidebars/sidebar.constants.js (renamed from ui/app/components/sidebars/sidebar.constants.js)0
-rw-r--r--ui/app/components/app/sidebars/tests/sidebars-component.test.js (renamed from ui/app/components/sidebars/tests/sidebars-component.test.js)9
-rw-r--r--ui/app/components/app/signature-request.js (renamed from ui/app/components/signature-request.js)128
-rw-r--r--ui/app/components/app/tab-bar.js37
-rw-r--r--ui/app/components/app/token-cell.js (renamed from ui/app/components/token-cell.js)22
-rw-r--r--ui/app/components/app/token-list.js (renamed from ui/app/components/token-list.js)8
-rw-r--r--ui/app/components/app/transaction-action/index.js (renamed from ui/app/components/transaction-action/index.js)0
-rw-r--r--ui/app/components/app/transaction-action/tests/transaction-action.component.test.js (renamed from ui/app/components/transaction-action/tests/transaction-action.component.test.js)0
-rw-r--r--ui/app/components/app/transaction-action/transaction-action.component.js (renamed from ui/app/components/transaction-action/transaction-action.component.js)4
-rw-r--r--ui/app/components/app/transaction-activity-log/index.js (renamed from ui/app/components/transaction-activity-log/index.js)0
-rw-r--r--ui/app/components/app/transaction-activity-log/index.scss (renamed from ui/app/components/transaction-activity-log/index.scss)40
-rw-r--r--ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.component.test.js101
-rw-r--r--ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.container.test.js (renamed from ui/app/components/transaction-activity-log/tests/transaction-activity-log.container.test.js)0
-rw-r--r--ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.util.test.js335
-rw-r--r--ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/index.js1
-rw-r--r--ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/transaction-activity-log-icon.component.js55
-rw-r--r--ui/app/components/app/transaction-activity-log/transaction-activity-log.component.js131
-rw-r--r--ui/app/components/app/transaction-activity-log/transaction-activity-log.constants.js13
-rw-r--r--ui/app/components/app/transaction-activity-log/transaction-activity-log.container.js44
-rw-r--r--ui/app/components/app/transaction-activity-log/transaction-activity-log.util.js233
-rw-r--r--ui/app/components/app/transaction-breakdown/index.js (renamed from ui/app/components/transaction-breakdown/index.js)0
-rw-r--r--ui/app/components/app/transaction-breakdown/index.scss (renamed from ui/app/components/transaction-breakdown/index.scss)9
-rw-r--r--ui/app/components/app/transaction-breakdown/tests/transaction-breakdown.component.test.js (renamed from ui/app/components/transaction-breakdown/tests/transaction-breakdown.component.test.js)4
-rw-r--r--ui/app/components/app/transaction-breakdown/transaction-breakdown-row/index.js (renamed from ui/app/components/transaction-breakdown/transaction-breakdown-row/index.js)0
-rw-r--r--ui/app/components/app/transaction-breakdown/transaction-breakdown-row/index.scss (renamed from ui/app/components/transaction-breakdown/transaction-breakdown-row/index.scss)0
-rw-r--r--ui/app/components/app/transaction-breakdown/transaction-breakdown-row/tests/transaction-breakdown-row.component.test.js (renamed from ui/app/components/transaction-breakdown/transaction-breakdown-row/tests/transaction-breakdown-row.component.test.js)2
-rw-r--r--ui/app/components/app/transaction-breakdown/transaction-breakdown-row/transaction-breakdown-row.component.js (renamed from ui/app/components/transaction-breakdown/transaction-breakdown-row/transaction-breakdown-row.component.js)0
-rw-r--r--ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js106
-rw-r--r--ui/app/components/app/transaction-breakdown/transaction-breakdown.container.js29
-rw-r--r--ui/app/components/app/transaction-list-item-details/index.js (renamed from ui/app/components/transaction-list-item-details/index.js)0
-rw-r--r--ui/app/components/app/transaction-list-item-details/index.scss (renamed from ui/app/components/transaction-list-item-details/index.scss)16
-rw-r--r--ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js (renamed from ui/app/components/transaction-list-item-details/tests/transaction-list-item-details.component.test.js)27
-rw-r--r--ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js215
-rw-r--r--ui/app/components/app/transaction-list-item/index.js (renamed from ui/app/components/transaction-list-item/index.js)0
-rw-r--r--ui/app/components/app/transaction-list-item/index.scss (renamed from ui/app/components/transaction-list-item/index.scss)8
-rw-r--r--ui/app/components/app/transaction-list-item/transaction-list-item.component.js (renamed from ui/app/components/transaction-list-item/transaction-list-item.component.js)95
-rw-r--r--ui/app/components/app/transaction-list-item/transaction-list-item.container.js98
-rw-r--r--ui/app/components/app/transaction-list/index.js (renamed from ui/app/components/transaction-list/index.js)0
-rw-r--r--ui/app/components/app/transaction-list/index.scss (renamed from ui/app/components/transaction-list/index.scss)2
-rw-r--r--ui/app/components/app/transaction-list/transaction-list.component.js (renamed from ui/app/components/transaction-list/transaction-list.component.js)45
-rw-r--r--ui/app/components/app/transaction-list/transaction-list.container.js (renamed from ui/app/components/transaction-list/transaction-list.container.js)23
-rw-r--r--ui/app/components/app/transaction-status/index.js (renamed from ui/app/components/transaction-status/index.js)0
-rw-r--r--ui/app/components/app/transaction-status/index.scss (renamed from ui/app/components/transaction-status/index.scss)16
-rw-r--r--ui/app/components/app/transaction-status/tests/transaction-status.component.test.js33
-rw-r--r--ui/app/components/app/transaction-status/transaction-status.component.js (renamed from ui/app/components/transaction-status/transaction-status.component.js)12
-rw-r--r--ui/app/components/app/transaction-view-balance/index.js (renamed from ui/app/components/transaction-view-balance/index.js)0
-rw-r--r--ui/app/components/app/transaction-view-balance/index.scss (renamed from ui/app/components/transaction-view-balance/index.scss)41
-rw-r--r--ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js (renamed from ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js)7
-rw-r--r--ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js145
-rw-r--r--ui/app/components/app/transaction-view-balance/transaction-view-balance.container.js (renamed from ui/app/components/transaction-view-balance/transaction-view-balance.container.js)20
-rw-r--r--ui/app/components/app/transaction-view/index.js (renamed from ui/app/components/transaction-view/index.js)0
-rw-r--r--ui/app/components/app/transaction-view/index.scss (renamed from ui/app/components/transaction-view/index.scss)0
-rw-r--r--ui/app/components/app/transaction-view/transaction-view.component.js (renamed from ui/app/components/transaction-view/transaction-view.component.js)0
-rw-r--r--ui/app/components/app/ui-migration-annoucement/index.js1
-rw-r--r--ui/app/components/app/ui-migration-annoucement/index.scss22
-rw-r--r--ui/app/components/app/ui-migration-annoucement/ui-migration-annoucement.component.js33
-rw-r--r--ui/app/components/app/ui-migration-annoucement/ui-migration-announcement.container.js21
-rw-r--r--ui/app/components/app/user-preferenced-currency-display/index.js (renamed from ui/app/components/user-preferenced-currency-display/index.js)0
-rw-r--r--ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js (renamed from ui/app/components/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js)2
-rw-r--r--ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js (renamed from ui/app/components/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js)87
-rw-r--r--ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js (renamed from ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.component.js)5
-rw-r--r--ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js (renamed from ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.container.js)21
-rw-r--r--ui/app/components/app/user-preferenced-currency-input/index.js (renamed from ui/app/components/user-preferenced-currency-input/index.js)0
-rw-r--r--ui/app/components/app/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js (renamed from ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js)2
-rw-r--r--ui/app/components/app/user-preferenced-currency-input/tests/user-preferenced-currency-input.container.test.js (renamed from ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.container.test.js)0
-rw-r--r--ui/app/components/app/user-preferenced-currency-input/user-preferenced-currency-input.component.js (renamed from ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.component.js)2
-rw-r--r--ui/app/components/app/user-preferenced-currency-input/user-preferenced-currency-input.container.js (renamed from ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.container.js)2
-rw-r--r--ui/app/components/app/user-preferenced-token-input/index.js (renamed from ui/app/components/user-preferenced-token-input/index.js)0
-rw-r--r--ui/app/components/app/user-preferenced-token-input/tests/user-preferenced-token-input.component.test.js (renamed from ui/app/components/user-preferenced-token-input/tests/user-preferenced-token-input.component.test.js)2
-rw-r--r--ui/app/components/app/user-preferenced-token-input/tests/user-preferenced-token-input.container.test.js (renamed from ui/app/components/user-preferenced-token-input/tests/user-preferenced-token-input.container.test.js)0
-rw-r--r--ui/app/components/app/user-preferenced-token-input/user-preferenced-token-input.component.js (renamed from ui/app/components/user-preferenced-token-input/user-preferenced-token-input.component.js)2
-rw-r--r--ui/app/components/app/user-preferenced-token-input/user-preferenced-token-input.container.js (renamed from ui/app/components/user-preferenced-token-input/user-preferenced-token-input.container.js)2
-rw-r--r--ui/app/components/app/wallet-view.js (renamed from ui/app/components/wallet-view.js)36
-rw-r--r--ui/app/components/balance-component.js111
-rw-r--r--ui/app/components/confirm-page-container/index.scss5
-rw-r--r--ui/app/components/currency-input/index.scss7
-rw-r--r--ui/app/components/currency-input/tests/currency-input.container.test.js60
-rw-r--r--ui/app/components/dropdowns/account-dropdown-mini.js75
-rw-r--r--ui/app/components/dropdowns/components/network-dropdown-icon.js31
-rw-r--r--ui/app/components/index.scss63
-rw-r--r--ui/app/components/menu-bar/menu-bar.component.js63
-rw-r--r--ui/app/components/modals/index.scss9
-rw-r--r--ui/app/components/modals/welcome-beta/index.js1
-rw-r--r--ui/app/components/modals/welcome-beta/welcome-beta.container.js4
-rw-r--r--ui/app/components/pages/add-token/add-token.component.js319
-rw-r--r--ui/app/components/pages/add-token/add-token.container.js22
-rw-r--r--ui/app/components/pages/add-token/index.js2
-rw-r--r--ui/app/components/pages/add-token/index.scss25
-rw-r--r--ui/app/components/pages/add-token/token-list/index.js2
-rw-r--r--ui/app/components/pages/add-token/token-list/index.scss65
-rw-r--r--ui/app/components/pages/add-token/token-list/token-list-placeholder/index.js2
-rw-r--r--ui/app/components/pages/add-token/token-list/token-list-placeholder/index.scss23
-rw-r--r--ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js27
-rw-r--r--ui/app/components/pages/add-token/token-list/token-list.component.js60
-rw-r--r--ui/app/components/pages/add-token/token-list/token-list.container.js11
-rw-r--r--ui/app/components/pages/add-token/token-search/index.js2
-rw-r--r--ui/app/components/pages/add-token/token-search/token-search.component.js85
-rw-r--r--ui/app/components/pages/add-token/util.js13
-rw-r--r--ui/app/components/pages/authenticated.js34
-rw-r--r--ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js122
-rw-r--r--ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js29
-rw-r--r--ui/app/components/pages/confirm-add-suggested-token/index.js2
-rw-r--r--ui/app/components/pages/confirm-add-token/confirm-add-token.component.js117
-rw-r--r--ui/app/components/pages/confirm-add-token/confirm-add-token.container.js20
-rw-r--r--ui/app/components/pages/confirm-add-token/index.js2
-rw-r--r--ui/app/components/pages/confirm-add-token/index.scss69
-rw-r--r--ui/app/components/pages/confirm-approve/confirm-approve.component.js21
-rw-r--r--ui/app/components/pages/confirm-approve/confirm-approve.container.js15
-rw-r--r--ui/app/components/pages/confirm-approve/index.js1
-rw-r--r--ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.component.js64
-rw-r--r--ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.container.js12
-rw-r--r--ui/app/components/pages/confirm-deploy-contract/index.js1
-rw-r--r--ui/app/components/pages/confirm-send-ether/confirm-send-ether.component.js39
-rw-r--r--ui/app/components/pages/confirm-send-ether/confirm-send-ether.container.js45
-rw-r--r--ui/app/components/pages/confirm-send-ether/index.js1
-rw-r--r--ui/app/components/pages/confirm-send-token/confirm-send-token.component.js29
-rw-r--r--ui/app/components/pages/confirm-send-token/confirm-send-token.container.js52
-rw-r--r--ui/app/components/pages/confirm-send-token/index.js1
-rw-r--r--ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js119
-rw-r--r--ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js34
-rw-r--r--ui/app/components/pages/confirm-token-transaction-base/index.js2
-rw-r--r--ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js412
-rw-r--r--ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js203
-rw-r--r--ui/app/components/pages/confirm-transaction-base/index.js1
-rw-r--r--ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js92
-rw-r--r--ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.container.js22
-rw-r--r--ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.util.js4
-rw-r--r--ui/app/components/pages/confirm-transaction-switch/index.js2
-rw-r--r--ui/app/components/pages/confirm-transaction/confirm-transaction.component.js157
-rw-r--r--ui/app/components/pages/confirm-transaction/confirm-transaction.container.js33
-rw-r--r--ui/app/components/pages/confirm-transaction/index.js2
-rw-r--r--ui/app/components/pages/create-account/connect-hardware/account-list.js205
-rw-r--r--ui/app/components/pages/create-account/connect-hardware/connect-screen.js197
-rw-r--r--ui/app/components/pages/create-account/connect-hardware/index.js274
-rw-r--r--ui/app/components/pages/create-account/import-account/index.js96
-rw-r--r--ui/app/components/pages/create-account/import-account/json.js159
-rw-r--r--ui/app/components/pages/create-account/import-account/private-key.js112
-rw-r--r--ui/app/components/pages/create-account/import-account/seed.js35
-rw-r--r--ui/app/components/pages/create-account/index.js113
-rw-r--r--ui/app/components/pages/create-account/new-account.js108
-rw-r--r--ui/app/components/pages/home/home.component.js87
-rw-r--r--ui/app/components/pages/home/home.container.js32
-rw-r--r--ui/app/components/pages/home/index.js1
-rw-r--r--ui/app/components/pages/index.scss7
-rw-r--r--ui/app/components/pages/initialized.js25
-rw-r--r--ui/app/components/pages/keychains/restore-vault.js189
-rw-r--r--ui/app/components/pages/keychains/reveal-seed.js177
-rw-r--r--ui/app/components/pages/metamask-route.js28
-rw-r--r--ui/app/components/pages/notice.js203
-rw-r--r--ui/app/components/pages/provider-approval/index.js1
-rw-r--r--ui/app/components/pages/provider-approval/provider-approval.component.js28
-rw-r--r--ui/app/components/pages/provider-approval/provider-approval.container.js12
-rw-r--r--ui/app/components/pages/settings/index.js1
-rw-r--r--ui/app/components/pages/settings/index.scss80
-rw-r--r--ui/app/components/pages/settings/info-tab/index.js1
-rw-r--r--ui/app/components/pages/settings/info-tab/index.scss56
-rw-r--r--ui/app/components/pages/settings/info-tab/info-tab.component.js136
-rw-r--r--ui/app/components/pages/settings/settings-tab/index.js1
-rw-r--r--ui/app/components/pages/settings/settings-tab/index.scss69
-rw-r--r--ui/app/components/pages/settings/settings-tab/settings-tab.component.js544
-rw-r--r--ui/app/components/pages/settings/settings-tab/settings-tab.container.js74
-rw-r--r--ui/app/components/pages/settings/settings.component.js54
-rw-r--r--ui/app/components/pages/unlock-page/index.js2
-rw-r--r--ui/app/components/pages/unlock-page/index.scss52
-rw-r--r--ui/app/components/pages/unlock-page/unlock-page.component.js179
-rw-r--r--ui/app/components/pages/unlock-page/unlock-page.container.js31
-rw-r--r--ui/app/components/send/account-list-item/account-list-item.component.js76
-rw-r--r--ui/app/components/send/account-list-item/tests/account-list-item-container.test.js34
-rw-r--r--ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js22
-rw-r--r--ui/app/components/send/send-content/send-content-README.md0
-rw-r--r--ui/app/components/send/send-content/send-content.scss0
-rw-r--r--ui/app/components/send/send-content/send-from-row/from-dropdown/from-dropdown-README.md0
-rw-r--r--ui/app/components/send/send-content/send-from-row/from-dropdown/from-dropdown.component.js46
-rw-r--r--ui/app/components/send/send-content/send-from-row/from-dropdown/from-dropdown.scss0
-rw-r--r--ui/app/components/send/send-content/send-from-row/from-dropdown/index.js1
-rw-r--r--ui/app/components/send/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js88
-rw-r--r--ui/app/components/send/send-content/send-from-row/send-from-row-README.md0
-rw-r--r--ui/app/components/send/send-content/send-from-row/send-from-row.component.js63
-rw-r--r--ui/app/components/send/send-content/send-from-row/send-from-row.container.js46
-rw-r--r--ui/app/components/send/send-content/send-from-row/tests/send-from-row-component.test.js121
-rw-r--r--ui/app/components/send/send-content/send-from-row/tests/send-from-row-container.test.js110
-rw-r--r--ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js48
-rw-r--r--ui/app/components/send/send-content/send-gas-row/send-gas-row.container.js27
-rw-r--r--ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-container.test.js70
-rw-r--r--ui/app/components/send/send-content/send-to-row/send-to-row.utils.js21
-rw-r--r--ui/app/components/send/send-content/send-to-row/tests/send-to-row-utils.test.js57
-rw-r--r--ui/app/components/sidebars/sidebar.component.js49
-rw-r--r--ui/app/components/tab-bar.js33
-rw-r--r--ui/app/components/token-input/tests/token-input.container.test.js129
-rw-r--r--ui/app/components/transaction-activity-log/tests/transaction-activity-log.component.test.js35
-rw-r--r--ui/app/components/transaction-activity-log/tests/transaction-activity-log.util.test.js208
-rw-r--r--ui/app/components/transaction-activity-log/transaction-activity-log.component.js96
-rw-r--r--ui/app/components/transaction-activity-log/transaction-activity-log.container.js12
-rw-r--r--ui/app/components/transaction-activity-log/transaction-activity-log.util.js93
-rw-r--r--ui/app/components/transaction-breakdown/transaction-breakdown.component.js100
-rw-r--r--ui/app/components/transaction-breakdown/transaction-breakdown.container.js11
-rw-r--r--ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js111
-rw-r--r--ui/app/components/transaction-list-item/transaction-list-item.container.js38
-rw-r--r--ui/app/components/transaction-view-balance/transaction-view-balance.component.js98
-rw-r--r--ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js84
-rw-r--r--ui/app/components/ui/account-dropdown-mini/index.js1
-rw-r--r--ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js107
-rw-r--r--ui/app/components/ui/alert/index.js (renamed from ui/app/components/alert/index.js)0
-rw-r--r--ui/app/components/ui/balance/balance.component.js92
-rw-r--r--ui/app/components/ui/balance/balance.container.js32
-rw-r--r--ui/app/components/ui/balance/index.js1
-rw-r--r--ui/app/components/ui/breadcrumbs/breadcrumbs.component.js29
-rw-r--r--ui/app/components/ui/breadcrumbs/index.js1
-rw-r--r--ui/app/components/ui/breadcrumbs/index.scss15
-rw-r--r--ui/app/components/ui/breadcrumbs/tests/breadcrumbs.component.test.js22
-rw-r--r--ui/app/components/ui/button-group/button-group.component.js (renamed from ui/app/components/button-group/button-group.component.js)14
-rw-r--r--ui/app/components/ui/button-group/button-group.stories.js (renamed from ui/app/components/button-group/button-group.stories.js)2
-rw-r--r--ui/app/components/ui/button-group/index.js (renamed from ui/app/components/button-group/index.js)0
-rw-r--r--ui/app/components/ui/button-group/index.scss (renamed from ui/app/components/button-group/index.scss)0
-rw-r--r--ui/app/components/ui/button-group/tests/button-group-component.test.js (renamed from ui/app/components/button-group/tests/button-group-component.test.js)14
-rw-r--r--ui/app/components/ui/button/button.component.js (renamed from ui/app/components/button/button.component.js)8
-rw-r--r--ui/app/components/ui/button/button.stories.js (renamed from ui/app/components/button/button.stories.js)2
-rw-r--r--ui/app/components/ui/button/index.js (renamed from ui/app/components/button/index.js)0
-rw-r--r--ui/app/components/ui/card/card.component.js (renamed from ui/app/components/card/card.component.js)0
-rw-r--r--ui/app/components/ui/card/index.js (renamed from ui/app/components/card/index.js)0
-rw-r--r--ui/app/components/ui/card/index.scss (renamed from ui/app/components/card/index.scss)0
-rw-r--r--ui/app/components/ui/card/tests/card.component.test.js (renamed from ui/app/components/card/tests/card.component.test.js)0
-rw-r--r--ui/app/components/ui/copyButton.js (renamed from ui/app/components/copyButton.js)0
-rw-r--r--ui/app/components/ui/currency-display/currency-display.component.js (renamed from ui/app/components/currency-display/currency-display.component.js)9
-rw-r--r--ui/app/components/ui/currency-display/currency-display.container.js (renamed from ui/app/components/currency-display/currency-display.container.js)21
-rw-r--r--ui/app/components/ui/currency-display/index.js (renamed from ui/app/components/currency-display/index.js)0
-rw-r--r--ui/app/components/ui/currency-display/index.scss (renamed from ui/app/components/currency-display/index.scss)0
-rw-r--r--ui/app/components/ui/currency-display/tests/currency-display.component.test.js (renamed from ui/app/components/currency-display/tests/currency-display.component.test.js)0
-rw-r--r--ui/app/components/ui/currency-display/tests/currency-display.container.test.js (renamed from ui/app/components/currency-display/tests/currency-display.container.test.js)2
-rw-r--r--ui/app/components/ui/currency-input/currency-input.component.js (renamed from ui/app/components/currency-input/currency-input.component.js)77
-rw-r--r--ui/app/components/ui/currency-input/currency-input.container.js (renamed from ui/app/components/currency-input/currency-input.container.js)11
-rw-r--r--ui/app/components/ui/currency-input/index.js (renamed from ui/app/components/currency-input/index.js)0
-rw-r--r--ui/app/components/ui/currency-input/index.scss26
-rw-r--r--ui/app/components/ui/currency-input/tests/currency-input.component.test.js (renamed from ui/app/components/currency-input/tests/currency-input.component.test.js)100
-rw-r--r--ui/app/components/ui/currency-input/tests/currency-input.container.test.js170
-rw-r--r--ui/app/components/ui/editable-label.js (renamed from ui/app/components/editable-label.js)0
-rw-r--r--ui/app/components/ui/error-message/error-message.component.js (renamed from ui/app/components/error-message/error-message.component.js)0
-rw-r--r--ui/app/components/ui/error-message/index.js (renamed from ui/app/components/error-message/index.js)0
-rw-r--r--ui/app/components/ui/error-message/index.scss (renamed from ui/app/components/error-message/index.scss)0
-rw-r--r--ui/app/components/ui/error-message/tests/error-message.component.test.js (renamed from ui/app/components/error-message/tests/error-message.component.test.js)0
-rw-r--r--ui/app/components/ui/eth-balance.js (renamed from ui/app/components/eth-balance.js)2
-rw-r--r--ui/app/components/ui/export-text-container/export-text-container.component.js (renamed from ui/app/components/export-text-container/export-text-container.component.js)2
-rw-r--r--ui/app/components/ui/export-text-container/index.js (renamed from ui/app/components/export-text-container/index.js)0
-rw-r--r--ui/app/components/ui/export-text-container/index.scss (renamed from ui/app/components/export-text-container/index.scss)0
-rw-r--r--ui/app/components/ui/fiat-value.js (renamed from ui/app/components/fiat-value.js)2
-rw-r--r--ui/app/components/ui/hex-to-decimal/hex-to-decimal.component.js (renamed from ui/app/components/hex-to-decimal/hex-to-decimal.component.js)2
-rw-r--r--ui/app/components/ui/hex-to-decimal/index.js (renamed from ui/app/components/hex-to-decimal/index.js)0
-rw-r--r--ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js (renamed from ui/app/components/hex-to-decimal/tests/hex-to-decimal.component.test.js)0
-rw-r--r--ui/app/components/ui/identicon/identicon.component.js (renamed from ui/app/components/identicon/identicon.component.js)4
-rw-r--r--ui/app/components/ui/identicon/identicon.container.js (renamed from ui/app/components/identicon/identicon.container.js)0
-rw-r--r--ui/app/components/ui/identicon/index.js (renamed from ui/app/components/identicon/index.js)0
-rw-r--r--ui/app/components/ui/identicon/index.scss (renamed from ui/app/components/identicon/index.scss)0
-rw-r--r--ui/app/components/ui/identicon/tests/identicon.component.test.js (renamed from ui/app/components/identicon/tests/identicon.component.test.js)0
-rw-r--r--ui/app/components/ui/jazzicon/index.js (renamed from ui/app/components/jazzicon/index.js)0
-rw-r--r--ui/app/components/ui/jazzicon/jazzicon.component.js (renamed from ui/app/components/jazzicon/jazzicon.component.js)2
-rw-r--r--ui/app/components/ui/loading-screen/index.js (renamed from ui/app/components/loading-screen/index.js)0
-rw-r--r--ui/app/components/ui/loading-screen/loading-screen.component.js (renamed from ui/app/components/loading-screen/loading-screen.component.js)0
-rw-r--r--ui/app/components/ui/lock-icon/index.js1
-rw-r--r--ui/app/components/ui/lock-icon/lock-icon.component.js32
-rw-r--r--ui/app/components/ui/mascot.js (renamed from ui/app/components/mascot.js)0
-rw-r--r--ui/app/components/ui/page-container/index.js (renamed from ui/app/components/page-container/index.js)0
-rw-r--r--ui/app/components/ui/page-container/index.scss (renamed from ui/app/components/page-container/index.scss)9
-rw-r--r--ui/app/components/ui/page-container/page-container-content.component.js (renamed from ui/app/components/page-container/page-container-content.component.js)0
-rw-r--r--ui/app/components/ui/page-container/page-container-footer/index.js (renamed from ui/app/components/page-container/page-container-footer/index.js)0
-rw-r--r--ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js (renamed from ui/app/components/page-container/page-container-footer/page-container-footer.component.js)6
-rw-r--r--ui/app/components/ui/page-container/page-container-footer/tests/page-container-footer.component.test.js (renamed from ui/app/components/page-container/page-container-footer/tests/page-container-footer.component.test.js)0
-rw-r--r--ui/app/components/ui/page-container/page-container-header/index.js (renamed from ui/app/components/page-container/page-container-header/index.js)0
-rw-r--r--ui/app/components/ui/page-container/page-container-header/page-container-header.component.js (renamed from ui/app/components/page-container/page-container-header/page-container-header.component.js)13
-rw-r--r--ui/app/components/ui/page-container/page-container-header/tests/page-container-header.component.test.js (renamed from ui/app/components/page-container/page-container-header/tests/page-container-header.component.test.js)0
-rw-r--r--ui/app/components/ui/page-container/page-container.component.js (renamed from ui/app/components/page-container/page-container.component.js)29
-rw-r--r--ui/app/components/ui/page-container/tests/page-container.component.test.js (renamed from ui/app/components/send/account-list-item/account-list-item.scss)0
-rw-r--r--ui/app/components/ui/qr-code.js (renamed from ui/app/components/qr-code.js)10
-rw-r--r--ui/app/components/ui/readonly-input.js (renamed from ui/app/components/readonly-input.js)0
-rw-r--r--ui/app/components/ui/sender-to-recipient/index.js (renamed from ui/app/components/sender-to-recipient/index.js)0
-rw-r--r--ui/app/components/ui/sender-to-recipient/index.scss (renamed from ui/app/components/sender-to-recipient/index.scss)55
-rw-r--r--ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js (renamed from ui/app/components/sender-to-recipient/sender-to-recipient.component.js)37
-rw-r--r--ui/app/components/ui/sender-to-recipient/sender-to-recipient.constants.js (renamed from ui/app/components/sender-to-recipient/sender-to-recipient.constants.js)1
-rw-r--r--ui/app/components/ui/spinner/index.js (renamed from ui/app/components/spinner/index.js)0
-rw-r--r--ui/app/components/ui/spinner/spinner.component.js (renamed from ui/app/components/spinner/spinner.component.js)0
-rw-r--r--ui/app/components/ui/tabs/index.js (renamed from ui/app/components/tabs/index.js)0
-rw-r--r--ui/app/components/ui/tabs/index.scss (renamed from ui/app/components/tabs/index.scss)2
-rw-r--r--ui/app/components/ui/tabs/tab/index.js (renamed from ui/app/components/tabs/tab/index.js)0
-rw-r--r--ui/app/components/ui/tabs/tab/index.scss (renamed from ui/app/components/tabs/tab/index.scss)0
-rw-r--r--ui/app/components/ui/tabs/tab/tab.component.js (renamed from ui/app/components/tabs/tab/tab.component.js)0
-rw-r--r--ui/app/components/ui/tabs/tabs.component.js (renamed from ui/app/components/tabs/tabs.component.js)0
-rw-r--r--ui/app/components/ui/text-field/index.js (renamed from ui/app/components/text-field/index.js)0
-rw-r--r--ui/app/components/ui/text-field/text-field.component.js (renamed from ui/app/components/text-field/text-field.component.js)0
-rw-r--r--ui/app/components/ui/text-field/text-field.stories.js (renamed from ui/app/components/text-field/text-field.stories.js)2
-rw-r--r--ui/app/components/ui/token-balance/index.js (renamed from ui/app/components/token-balance/index.js)0
-rw-r--r--ui/app/components/ui/token-balance/index.scss14
-rw-r--r--ui/app/components/ui/token-balance/token-balance.component.js (renamed from ui/app/components/token-balance/token-balance.component.js)12
-rw-r--r--ui/app/components/ui/token-balance/token-balance.container.js (renamed from ui/app/components/token-balance/token-balance.container.js)4
-rw-r--r--ui/app/components/ui/token-currency-display/index.js (renamed from ui/app/components/token-currency-display/index.js)0
-rw-r--r--ui/app/components/ui/token-currency-display/token-currency-display.component.js (renamed from ui/app/components/token-currency-display/token-currency-display.component.js)21
-rw-r--r--ui/app/components/ui/token-input/index.js (renamed from ui/app/components/token-input/index.js)0
-rw-r--r--ui/app/components/ui/token-input/tests/token-input.component.test.js (renamed from ui/app/components/token-input/tests/token-input.component.test.js)42
-rw-r--r--ui/app/components/ui/token-input/tests/token-input.container.test.js255
-rw-r--r--ui/app/components/ui/token-input/token-input.component.js (renamed from ui/app/components/token-input/token-input.component.js)25
-rw-r--r--ui/app/components/ui/token-input/token-input.container.js (renamed from ui/app/components/token-input/token-input.container.js)5
-rw-r--r--ui/app/components/ui/tooltip-v2.js (renamed from ui/app/components/tooltip-v2.js)6
-rw-r--r--ui/app/components/ui/tooltip.js (renamed from ui/app/components/tooltip.js)0
-rw-r--r--ui/app/components/ui/unit-input/index.js (renamed from ui/app/components/unit-input/index.js)0
-rw-r--r--ui/app/components/ui/unit-input/index.scss (renamed from ui/app/components/unit-input/index.scss)11
-rw-r--r--ui/app/components/ui/unit-input/tests/unit-input.component.test.js (renamed from ui/app/components/unit-input/tests/unit-input.component.test.js)0
-rw-r--r--ui/app/components/ui/unit-input/unit-input.component.js (renamed from ui/app/components/unit-input/unit-input.component.js)48
616 files changed, 11128 insertions, 9260 deletions
diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js
deleted file mode 100644
index 865207487..000000000
--- a/ui/app/components/account-export.js
+++ /dev/null
@@ -1,138 +0,0 @@
-const Component = require('react').Component
-const h = require('react-hyperscript')
-const PropTypes = require('prop-types')
-const inherits = require('util').inherits
-const exportAsFile = require('../util').exportAsFile
-const copyToClipboard = require('copy-to-clipboard')
-const actions = require('../actions')
-const ethUtil = require('ethereumjs-util')
-const connect = require('react-redux').connect
-
-ExportAccountView.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect(mapStateToProps)(ExportAccountView)
-
-
-inherits(ExportAccountView, Component)
-function ExportAccountView () {
- Component.call(this)
-}
-
-function mapStateToProps (state) {
- return {
- warning: state.appState.warning,
- }
-}
-
-ExportAccountView.prototype.render = function () {
- const state = this.props
- const accountDetail = state.accountDetail
- const nickname = state.identities[state.address].name
-
- if (!accountDetail) return h('div')
- const accountExport = accountDetail.accountExport
-
- const notExporting = accountExport === 'none'
- const exportRequested = accountExport === 'requested'
- const accountExported = accountExport === 'completed'
-
- if (notExporting) return h('div')
-
- if (exportRequested) {
- const warning = this.context.t('exportPrivateKeyWarning')
- return (
- h('div', {
- style: {
- display: 'inline-block',
- textAlign: 'center',
- },
- },
- [
- h('div', {
- key: 'exporting',
- style: {
- margin: '0 20px',
- },
- }, [
- h('p.error', warning),
- h('input#exportAccount.sizing-input', {
- type: 'password',
- placeholder: this.context.t('confirmPassword').toLowerCase(),
- onKeyPress: this.onExportKeyPress.bind(this),
- style: {
- position: 'relative',
- top: '1.5px',
- marginBottom: '7px',
- },
- }),
- ]),
- h('div', {
- key: 'buttons',
- style: {
- margin: '0 20px',
- },
- },
- [
- h('button', {
- onClick: () => this.onExportKeyPress({ key: 'Enter', preventDefault: () => {} }),
- style: {
- marginRight: '10px',
- },
- }, this.context.t('submit')),
- h('button', {
- onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)),
- }, this.context.t('cancel')),
- ]),
- (this.props.warning) && (
- h('span.error', {
- style: {
- margin: '20px',
- },
- }, this.props.warning.split('-'))
- ),
- ])
- )
- }
-
- if (accountExported) {
- const plainKey = ethUtil.stripHexPrefix(accountDetail.privateKey)
-
- return h('div.privateKey', {
- style: {
- margin: '0 20px',
- },
- }, [
- h('label', this.context.t('copyPrivateKey') + ':'),
- h('p.error.cursor-pointer', {
- style: {
- textOverflow: 'ellipsis',
- overflow: 'hidden',
- webkitUserSelect: 'text',
- maxWidth: '275px',
- },
- onClick: function (event) {
- copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey))
- },
- }, plainKey),
- h('button', {
- onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)),
- }, this.context.t('done')),
- h('button', {
- style: {
- marginLeft: '10px',
- },
- onClick: () => exportAsFile(`MetaMask ${nickname} Private Key`, plainKey),
- }, this.context.t('saveAsFile')),
- ])
- }
-}
-
-ExportAccountView.prototype.onExportKeyPress = function (event) {
- if (event.key !== 'Enter') return
- event.preventDefault()
-
- const input = document.getElementById('exportAccount').value
- this.props.dispatch(actions.exportAccount(input, this.props.address))
-}
diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js
deleted file mode 100644
index 94eae8d07..000000000
--- a/ui/app/components/account-menu/index.js
+++ /dev/null
@@ -1,248 +0,0 @@
-const inherits = require('util').inherits
-const Component = require('react').Component
-const connect = require('react-redux').connect
-const { compose } = require('recompose')
-const { withRouter } = require('react-router-dom')
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const actions = require('../../actions')
-const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
-const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
-const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
-const Tooltip = require('../tooltip')
-import Identicon from '../identicon'
-import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
-import { PRIMARY } from '../../constants/common'
-
-const {
- SETTINGS_ROUTE,
- INFO_ROUTE,
- NEW_ACCOUNT_ROUTE,
- IMPORT_ACCOUNT_ROUTE,
- CONNECT_HARDWARE_ROUTE,
- DEFAULT_ROUTE,
-} = require('../../routes')
-
-module.exports = compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(AccountMenu)
-
-AccountMenu.contextTypes = {
- t: PropTypes.func,
-}
-
-inherits(AccountMenu, Component)
-function AccountMenu () { Component.call(this) }
-
-function mapStateToProps (state) {
- return {
- selectedAddress: state.metamask.selectedAddress,
- isAccountMenuOpen: state.metamask.isAccountMenuOpen,
- keyrings: state.metamask.keyrings,
- identities: state.metamask.identities,
- accounts: state.metamask.accounts,
- }
-}
-
-function mapDispatchToProps (dispatch) {
- return {
- toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
- showAccountDetail: address => {
- dispatch(actions.showAccountDetail(address))
- dispatch(actions.hideSidebar())
- dispatch(actions.toggleAccountMenu())
- },
- lockMetamask: () => {
- dispatch(actions.lockMetamask())
- dispatch(actions.hideWarning())
- dispatch(actions.hideSidebar())
- dispatch(actions.toggleAccountMenu())
- },
- showConfigPage: () => {
- dispatch(actions.showConfigPage())
- dispatch(actions.hideSidebar())
- dispatch(actions.toggleAccountMenu())
- },
- showInfoPage: () => {
- dispatch(actions.showInfoPage())
- dispatch(actions.hideSidebar())
- dispatch(actions.toggleAccountMenu())
- },
- showRemoveAccountConfirmationModal: (identity) => {
- return dispatch(actions.showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity }))
- },
- }
-}
-
-AccountMenu.prototype.render = function () {
- const {
- isAccountMenuOpen,
- toggleAccountMenu,
- lockMetamask,
- history,
- } = this.props
-
- return h(Menu, { className: 'account-menu', isShowing: isAccountMenuOpen }, [
- h(CloseArea, { onClick: toggleAccountMenu }),
- h(Item, {
- className: 'account-menu__header',
- }, [
- this.context.t('myAccounts'),
- h('button.account-menu__logout-button', {
- onClick: () => {
- lockMetamask()
- history.push(DEFAULT_ROUTE)
- },
- }, this.context.t('logout')),
- ]),
- h(Divider),
- h('div.account-menu__accounts', this.renderAccounts()),
- h(Divider),
- h(Item, {
- onClick: () => {
- toggleAccountMenu()
- history.push(NEW_ACCOUNT_ROUTE)
- },
- icon: h('img.account-menu__item-icon', { src: 'images/plus-btn-white.svg' }),
- text: this.context.t('createAccount'),
- }),
- h(Item, {
- onClick: () => {
- toggleAccountMenu()
- history.push(IMPORT_ACCOUNT_ROUTE)
- },
- icon: h('img.account-menu__item-icon', { src: 'images/import-account.svg' }),
- text: this.context.t('importAccount'),
- }),
- h(Item, {
- onClick: () => {
- toggleAccountMenu()
- if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
- global.platform.openExtensionInBrowser(CONNECT_HARDWARE_ROUTE)
- } else {
- history.push(CONNECT_HARDWARE_ROUTE)
- }
- },
- icon: h('img.account-menu__item-icon', { src: 'images/connect-icon.svg' }),
- text: this.context.t('connectHardwareWallet'),
- }),
- h(Divider),
- h(Item, {
- onClick: () => {
- toggleAccountMenu()
- history.push(INFO_ROUTE)
- },
- icon: h('img', { src: 'images/mm-info-icon.svg' }),
- text: this.context.t('infoHelp'),
- }),
- h(Item, {
- onClick: () => {
- toggleAccountMenu()
- history.push(SETTINGS_ROUTE)
- },
- icon: h('img.account-menu__item-icon', { src: 'images/settings.svg' }),
- text: this.context.t('settings'),
- }),
- ])
-}
-
-AccountMenu.prototype.renderAccounts = function () {
- const {
- identities,
- accounts,
- selectedAddress,
- keyrings,
- showAccountDetail,
- } = this.props
-
- const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), [])
- return accountOrder.filter(address => !!identities[address]).map((address) => {
-
- const identity = identities[address]
- const isSelected = identity.address === selectedAddress
-
- const balanceValue = accounts[address] ? accounts[address].balance : ''
- const simpleAddress = identity.address.substring(2).toLowerCase()
-
- const keyring = keyrings.find((kr) => {
- return kr.accounts.includes(simpleAddress) ||
- kr.accounts.includes(identity.address)
- })
-
- return h(
- 'div.account-menu__account.menu__item--clickable',
- { onClick: () => showAccountDetail(identity.address) },
- [
- h('div.account-menu__check-mark', [
- isSelected ? h('div.account-menu__check-mark-icon') : null,
- ]),
-
- h(
- Identicon,
- {
- address: identity.address,
- diameter: 24,
- },
- ),
-
- h('div.account-menu__account-info', [
- h('div.account-menu__name', identity.name || ''),
- h(UserPreferencedCurrencyDisplay, {
- className: 'account-menu__balance',
- value: balanceValue,
- type: PRIMARY,
- }),
- ]),
-
- this.renderKeyringType(keyring),
- this.renderRemoveAccount(keyring, identity),
- ],
- )
- })
-}
-
-AccountMenu.prototype.renderRemoveAccount = function (keyring, identity) {
- // Any account that's not from the HD wallet Keyring can be removed
- const type = keyring.type
- const isRemovable = type !== 'HD Key Tree'
- if (isRemovable) {
- return h(Tooltip, {
- title: this.context.t('removeAccount'),
- position: 'bottom',
- }, [
- h('a.remove-account-icon', {
- onClick: (e) => this.removeAccount(e, identity),
- }, ''),
- ])
- }
- return null
-}
-
-AccountMenu.prototype.removeAccount = function (e, identity) {
- e.preventDefault()
- e.stopPropagation()
- const { showRemoveAccountConfirmationModal } = this.props
- showRemoveAccountConfirmationModal(identity)
-}
-
-AccountMenu.prototype.renderKeyringType = function (keyring) {
- try { // Sometimes keyrings aren't loaded yet:
- const type = keyring.type
- let label
- switch (type) {
- case 'Trezor Hardware':
- case 'Ledger Hardware':
- label = this.context.t('hardware')
- break
- case 'Simple Key Pair':
- label = this.context.t('imported')
- break
- default:
- label = ''
- }
-
- return label !== '' ? h('.keyring-label.allcaps', label) : null
-
- } catch (e) { return }
-}
diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/app/account-dropdowns.js
index 06376e48b..e02d17e54 100644
--- a/ui/app/components/account-dropdowns.js
+++ b/ui/app/components/app/account-dropdowns.js
@@ -1,15 +1,15 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
-const actions = require('../actions')
+const actions = require('../../store/actions')
const genAccountLink = require('etherscan-link').createAccountLink
const connect = require('react-redux').connect
const Dropdown = require('./dropdown').Dropdown
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
const copyToClipboard = require('copy-to-clipboard')
-const { checksumAddress } = require('../util')
+const { checksumAddress } = require('../../helpers/utils/util')
-import Identicon from './identicon'
+import Identicon from '../ui/identicon'
class AccountDropdowns extends Component {
constructor (props) {
@@ -233,6 +233,7 @@ class AccountDropdowns extends Component {
}
render () {
+ const { metricsEvent } = this.context
const { style, enableAccountsSelector, enableAccountOptions } = this.props
const { optionsMenuActive, accountSelectorActive } = this.state
@@ -272,6 +273,17 @@ class AccountDropdowns extends Component {
fontSize: '1.8em',
},
onClick: (event) => {
+ metricsEvent({
+ eventOpts: {
+ category: 'Accounts',
+ action: 'userClick',
+ name: 'accountsOpenedMenu',
+ },
+ pageOpts: {
+ section: 'header',
+ component: 'accountDropdownIcon',
+ },
+ })
event.stopPropagation()
this.setState({
accountSelectorActive: false,
@@ -318,6 +330,7 @@ const mapDispatchToProps = (dispatch) => {
AccountDropdowns.contextTypes = {
t: PropTypes.func,
+ metricsEvent: PropTypes.func,
}
module.exports = {
diff --git a/ui/app/components/app/account-menu/account-menu.component.js b/ui/app/components/app/account-menu/account-menu.component.js
new file mode 100644
index 000000000..972ea492e
--- /dev/null
+++ b/ui/app/components/app/account-menu/account-menu.component.js
@@ -0,0 +1,340 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import debounce from 'lodash.debounce'
+import { Menu, Item, Divider, CloseArea } from '../dropdowns/components/menu'
+import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
+import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
+import Tooltip from '../../ui/tooltip'
+import Identicon from '../../ui/identicon'
+import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
+import { PRIMARY } from '../../../helpers/constants/common'
+import {
+ SETTINGS_ROUTE,
+ INFO_ROUTE,
+ NEW_ACCOUNT_ROUTE,
+ IMPORT_ACCOUNT_ROUTE,
+ CONNECT_HARDWARE_ROUTE,
+ DEFAULT_ROUTE,
+} from '../../../helpers/constants/routes'
+
+export default class AccountMenu extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ metricsEvent: PropTypes.func,
+ }
+
+ static propTypes = {
+ accounts: PropTypes.object,
+ history: PropTypes.object,
+ identities: PropTypes.object,
+ isAccountMenuOpen: PropTypes.bool,
+ prevIsAccountMenuOpen: PropTypes.bool,
+ keyrings: PropTypes.array,
+ lockMetamask: PropTypes.func,
+ selectedAddress: PropTypes.string,
+ showAccountDetail: PropTypes.func,
+ showRemoveAccountConfirmationModal: PropTypes.func,
+ toggleAccountMenu: PropTypes.func,
+ }
+
+ state = {
+ atAccountListBottom: false,
+ }
+
+ componentDidUpdate (prevProps) {
+ const { prevIsAccountMenuOpen } = prevProps
+ const { isAccountMenuOpen } = this.props
+
+ if (!prevIsAccountMenuOpen && isAccountMenuOpen) {
+ this.setAtAccountListBottom()
+ }
+ }
+
+ renderAccounts () {
+ const {
+ identities,
+ accounts,
+ selectedAddress,
+ keyrings,
+ showAccountDetail,
+ } = this.props
+
+ const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), [])
+
+ return accountOrder.filter(address => !!identities[address]).map(address => {
+ const identity = identities[address]
+ const isSelected = identity.address === selectedAddress
+
+ const balanceValue = accounts[address] ? accounts[address].balance : ''
+ const simpleAddress = identity.address.substring(2).toLowerCase()
+
+ const keyring = keyrings.find(kr => {
+ return kr.accounts.includes(simpleAddress) || kr.accounts.includes(identity.address)
+ })
+
+ return (
+ <div
+ className="account-menu__account menu__item--clickable"
+ onClick={() => {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Main Menu',
+ name: 'Switched Account',
+ },
+ })
+ showAccountDetail(identity.address)
+ }}
+ key={identity.address}
+ >
+ <div className="account-menu__check-mark">
+ { isSelected && <div className="account-menu__check-mark-icon" /> }
+ </div>
+ <Identicon
+ address={identity.address}
+ diameter={24}
+ />
+ <div className="account-menu__account-info">
+ <div className="account-menu__name">
+ { identity.name || '' }
+ </div>
+ <UserPreferencedCurrencyDisplay
+ className="account-menu__balance"
+ value={balanceValue}
+ type={PRIMARY}
+ />
+ </div>
+ { this.renderKeyringType(keyring) }
+ { this.renderRemoveAccount(keyring, identity) }
+ </div>
+ )
+ })
+ }
+
+ renderRemoveAccount (keyring, identity) {
+ const { t } = this.context
+ // Any account that's not from the HD wallet Keyring can be removed
+ const { type } = keyring
+ const isRemovable = type !== 'HD Key Tree'
+
+ return isRemovable && (
+ <Tooltip
+ title={t('removeAccount')}
+ position="bottom"
+ >
+ <a
+ className="remove-account-icon"
+ onClick={e => this.removeAccount(e, identity)}
+ />
+ </Tooltip>
+ )
+ }
+
+ removeAccount (e, identity) {
+ e.preventDefault()
+ e.stopPropagation()
+ const { showRemoveAccountConfirmationModal } = this.props
+ showRemoveAccountConfirmationModal(identity)
+ }
+
+ renderKeyringType (keyring) {
+ const { t } = this.context
+
+ // Sometimes keyrings aren't loaded yet
+ if (!keyring) {
+ return null
+ }
+
+ const { type } = keyring
+ let label
+
+ switch (type) {
+ case 'Trezor Hardware':
+ case 'Ledger Hardware':
+ label = t('hardware')
+ break
+ case 'Simple Key Pair':
+ label = t('imported')
+ break
+ }
+
+ return label && (
+ <div className="keyring-label allcaps">
+ { label }
+ </div>
+ )
+ }
+
+ setAtAccountListBottom = () => {
+ const target = document.querySelector('.account-menu__accounts')
+ const { scrollTop, offsetHeight, scrollHeight } = target
+ const atAccountListBottom = scrollTop + offsetHeight >= scrollHeight
+ this.setState({ atAccountListBottom })
+ }
+
+ onScroll = debounce(this.setAtAccountListBottom, 25)
+
+ handleScrollDown = e => {
+ e.stopPropagation()
+ const target = document.querySelector('.account-menu__accounts')
+ const { scrollHeight } = target
+ target.scroll({ left: 0, top: scrollHeight, behavior: 'smooth' })
+ this.setAtAccountListBottom()
+ }
+
+ renderScrollButton () {
+ const { accounts } = this.props
+ const { atAccountListBottom } = this.state
+
+ return !atAccountListBottom && Object.keys(accounts).length > 3 && (
+ <div
+ className="account-menu__scroll-button"
+ onClick={this.handleScrollDown}
+ >
+ <img
+ src="./images/icons/down-arrow.svg"
+ width={28}
+ height={28}
+ />
+ </div>
+ )
+ }
+
+ render () {
+ const { t } = this.context
+ const {
+ isAccountMenuOpen,
+ toggleAccountMenu,
+ lockMetamask,
+ history,
+ } = this.props
+ const { metricsEvent } = this.context
+
+ return (
+ <Menu
+ className="account-menu"
+ isShowing={isAccountMenuOpen}
+ >
+ <CloseArea onClick={toggleAccountMenu} />
+ <Item className="account-menu__header">
+ { t('myAccounts') }
+ <button
+ className="account-menu__logout-button"
+ onClick={() => {
+ lockMetamask()
+ history.push(DEFAULT_ROUTE)
+ }}
+ >
+ { t('logout') }
+ </button>
+ </Item>
+ <Divider />
+ <div className="account-menu__accounts-container">
+ <div
+ className="account-menu__accounts"
+ onScroll={this.onScroll}
+ >
+ { this.renderAccounts() }
+ </div>
+ { this.renderScrollButton() }
+ </div>
+ <Divider />
+ <Item
+ onClick={() => {
+ toggleAccountMenu()
+ metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Main Menu',
+ name: 'Clicked Create Account',
+ },
+ })
+ history.push(NEW_ACCOUNT_ROUTE)
+ }}
+ icon={
+ <img
+ className="account-menu__item-icon"
+ src="images/plus-btn-white.svg"
+ />
+ }
+ text={t('createAccount')}
+ />
+ <Item
+ onClick={() => {
+ toggleAccountMenu()
+ metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Main Menu',
+ name: 'Clicked Import Account',
+ },
+ })
+ history.push(IMPORT_ACCOUNT_ROUTE)
+ }}
+ icon={
+ <img
+ className="account-menu__item-icon"
+ src="images/import-account.svg"
+ />
+ }
+ text={t('importAccount')}
+ />
+ <Item
+ onClick={() => {
+ toggleAccountMenu()
+ metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Main Menu',
+ name: 'Clicked Connect Hardware',
+ },
+ })
+ if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
+ global.platform.openExtensionInBrowser(CONNECT_HARDWARE_ROUTE)
+ } else {
+ history.push(CONNECT_HARDWARE_ROUTE)
+ }
+ }}
+ icon={
+ <img
+ className="account-menu__item-icon"
+ src="images/connect-icon.svg"
+ />
+ }
+ text={t('connectHardwareWallet')}
+ />
+ <Divider />
+ <Item
+ onClick={() => {
+ toggleAccountMenu()
+ history.push(INFO_ROUTE)
+ }}
+ icon={
+ <img src="images/mm-info-icon.svg" />
+ }
+ text={t('infoHelp')}
+ />
+ <Item
+ onClick={() => {
+ toggleAccountMenu()
+ history.push(SETTINGS_ROUTE)
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Main Menu',
+ name: 'Opened Settings',
+ },
+ })
+ }}
+ icon={
+ <img
+ className="account-menu__item-icon"
+ src="images/settings.svg"
+ />
+ }
+ text={t('settings')}
+ />
+ </Menu>
+ )
+ }
+}
diff --git a/ui/app/components/app/account-menu/account-menu.container.js b/ui/app/components/app/account-menu/account-menu.container.js
new file mode 100644
index 000000000..ae2e28e76
--- /dev/null
+++ b/ui/app/components/app/account-menu/account-menu.container.js
@@ -0,0 +1,62 @@
+import { connect } from 'react-redux'
+import { compose } from 'recompose'
+import { withRouter } from 'react-router-dom'
+import {
+ toggleAccountMenu,
+ showAccountDetail,
+ hideSidebar,
+ lockMetamask,
+ hideWarning,
+ showConfigPage,
+ showInfoPage,
+ showModal,
+} from '../../../store/actions'
+import { getMetaMaskAccounts } from '../../../selectors/selectors'
+import AccountMenu from './account-menu.component'
+
+function mapStateToProps (state) {
+ const { metamask: { selectedAddress, isAccountMenuOpen, keyrings, identities } } = state
+
+ return {
+ selectedAddress,
+ isAccountMenuOpen,
+ keyrings,
+ identities,
+ accounts: getMetaMaskAccounts(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ toggleAccountMenu: () => dispatch(toggleAccountMenu()),
+ showAccountDetail: address => {
+ dispatch(showAccountDetail(address))
+ dispatch(hideSidebar())
+ dispatch(toggleAccountMenu())
+ },
+ lockMetamask: () => {
+ dispatch(lockMetamask())
+ dispatch(hideWarning())
+ dispatch(hideSidebar())
+ dispatch(toggleAccountMenu())
+ },
+ showConfigPage: () => {
+ dispatch(showConfigPage())
+ dispatch(hideSidebar())
+ dispatch(toggleAccountMenu())
+ },
+ showInfoPage: () => {
+ dispatch(showInfoPage())
+ dispatch(hideSidebar())
+ dispatch(toggleAccountMenu())
+ },
+ showRemoveAccountConfirmationModal: identity => {
+ return dispatch(showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity }))
+ },
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(AccountMenu)
diff --git a/ui/app/components/app/account-menu/index.js b/ui/app/components/app/account-menu/index.js
new file mode 100644
index 000000000..b2b4e4c6f
--- /dev/null
+++ b/ui/app/components/app/account-menu/index.js
@@ -0,0 +1 @@
+export { default } from './account-menu.container'
diff --git a/ui/app/components/app/account-menu/index.scss b/ui/app/components/app/account-menu/index.scss
new file mode 100644
index 000000000..9a61bf887
--- /dev/null
+++ b/ui/app/components/app/account-menu/index.scss
@@ -0,0 +1,177 @@
+.account-menu {
+ position: fixed;
+ z-index: 100;
+ top: 58px;
+ width: 310px;
+
+ @media screen and (max-width: 575px) {
+ right: calc(((100vw - 100%) / 2) + 8px);
+ }
+
+ @media screen and (min-width: 576px) {
+ right: calc((100vw - 85vw) / 2);
+ }
+
+ @media screen and (min-width: 769px) {
+ right: calc((100vw - 80vw) / 2);
+ }
+
+ @media screen and (min-width: 1281px) {
+ right: calc((100vw - 65vw) / 2);
+ }
+
+ &__icon {
+ margin-left: 20px;
+ cursor: pointer;
+
+ &--disabled {
+ cursor: initial;
+ }
+ }
+
+ &__header {
+ display: flex;
+ flex-flow: row nowrap;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ &__logout-button {
+ border: 1px solid $dusty-gray;
+ background-color: transparent;
+ color: $white;
+ border-radius: 4px;
+ font-size: 12px;
+ line-height: 23px;
+ padding: 0 24px;
+ }
+
+ &__item-icon {
+ width: 16px;
+ height: 16px;
+ }
+
+ &__accounts {
+ display: flex;
+ flex-flow: column nowrap;
+ overflow-y: auto;
+ max-height: 256px;
+ position: relative;
+ z-index: 200;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ @media screen and (max-width: 575px) {
+ max-height: 228px;
+ }
+
+ .keyring-label {
+ margin-top: 5px;
+ background-color: $dusty-gray;
+ color: $black;
+ font-weight: normal;
+ letter-spacing: .5px;
+ }
+ }
+
+ &__account {
+ display: flex;
+ flex-flow: row nowrap;
+ padding: 16px 14px;
+ flex: 0 0 auto;
+
+ @media screen and (max-width: 575px) {
+ padding: 12px 14px;
+ }
+
+ .remove-account-icon {
+ width: 15px;
+ margin-left: 10px;
+ height: 15px;
+ }
+
+ &:hover {
+ .remove-account-icon::after {
+ content: '\00D7';
+ font-size: 25px;
+ color: $white;
+ cursor: pointer;
+ position: absolute;
+ margin-top: -5px;
+ }
+ }
+ }
+
+ &__account-info {
+ flex: 1 0 auto;
+ display: flex;
+ flex-flow: column nowrap;
+ }
+
+ &__check-mark {
+ width: 14px;
+ margin-right: 12px;
+ flex: 0 0 auto;
+ }
+
+ &__check-mark-icon {
+ background-image: url("images/check-white.svg");
+ height: 18px;
+ width: 18px;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: contain;
+ margin: 3px 0;
+ }
+
+ .identicon {
+ margin: 0 12px 0 0;
+ flex: 0 0 auto;
+ }
+
+ &__name {
+ color: $white;
+ font-size: 18px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ max-width: 200px;
+ }
+
+ &__balance {
+ color: $dusty-gray;
+ font-size: 14px;
+ }
+
+ &__action {
+ font-size: 16px;
+ line-height: 18px;
+ cursor: pointer;
+ }
+
+ &__accounts-container {
+ position: relative;
+ }
+
+ &__scroll-button {
+ position: absolute;
+ bottom: 12px;
+ right: 12px;
+ height: 28px;
+ width: 28px;
+ border-radius: 14px;
+ background: #3f3f3f;
+ z-index: 201;
+ cursor: pointer;
+ opacity: .8;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &:hover {
+ opacity: 1;
+ }
+ }
+}
diff --git a/ui/app/components/account-panel.js b/ui/app/components/app/account-panel.js
index a379ed3ac..79882f34a 100644
--- a/ui/app/components/account-panel.js
+++ b/ui/app/components/app/account-panel.js
@@ -1,9 +1,9 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
-import Identicon from './identicon'
-const formatBalance = require('../util').formatBalance
-const addressSummary = require('../util').addressSummary
+import Identicon from '../ui/identicon'
+const formatBalance = require('../../helpers/utils/util').formatBalance
+const addressSummary = require('../../helpers/utils/util').addressSummary
module.exports = AccountPanel
diff --git a/ui/app/components/add-token-button/add-token-button.component.js b/ui/app/components/app/add-token-button/add-token-button.component.js
index 10887aed8..10887aed8 100644
--- a/ui/app/components/add-token-button/add-token-button.component.js
+++ b/ui/app/components/app/add-token-button/add-token-button.component.js
diff --git a/ui/app/components/add-token-button/index.js b/ui/app/components/app/add-token-button/index.js
index 15c4fe6ca..15c4fe6ca 100644
--- a/ui/app/components/add-token-button/index.js
+++ b/ui/app/components/app/add-token-button/index.js
diff --git a/ui/app/components/add-token-button/index.scss b/ui/app/components/app/add-token-button/index.scss
index 39f404716..39f404716 100644
--- a/ui/app/components/add-token-button/index.scss
+++ b/ui/app/components/app/add-token-button/index.scss
diff --git a/ui/app/components/app-header/app-header.component.js b/ui/app/components/app/app-header/app-header.component.js
index c82dc1de9..343e0daab 100644
--- a/ui/app/components/app-header/app-header.component.js
+++ b/ui/app/components/app/app-header/app-header.component.js
@@ -1,20 +1,13 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-import { matchPath } from 'react-router-dom'
-import Identicon from '../identicon'
-
-const {
- ENVIRONMENT_TYPE_NOTIFICATION,
- ENVIRONMENT_TYPE_POPUP,
-} = require('../../../../app/scripts/lib/enums')
-const { DEFAULT_ROUTE, INITIALIZE_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes')
+import Identicon from '../../ui/identicon'
+import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'
const NetworkIndicator = require('../network')
export default class AppHeader extends PureComponent {
static propTypes = {
history: PropTypes.object,
- location: PropTypes.object,
network: PropTypes.string,
provider: PropTypes.object,
networkDropdownOpen: PropTypes.bool,
@@ -23,10 +16,14 @@ export default class AppHeader extends PureComponent {
toggleAccountMenu: PropTypes.func,
selectedAddress: PropTypes.string,
isUnlocked: PropTypes.bool,
+ hideNetworkIndicator: PropTypes.bool,
+ disabled: PropTypes.bool,
+ isAccountMenuOpen: PropTypes.bool,
}
static contextTypes = {
t: PropTypes.func,
+ metricsEvent: PropTypes.func,
}
handleNetworkIndicatorClick (event) {
@@ -35,28 +32,40 @@ export default class AppHeader extends PureComponent {
const { networkDropdownOpen, showNetworkDropdown, hideNetworkDropdown } = this.props
- return networkDropdownOpen === false
- ? showNetworkDropdown()
- : hideNetworkDropdown()
- }
-
- isConfirming () {
- const { location } = this.props
-
- return Boolean(matchPath(location.pathname, {
- path: CONFIRM_TRANSACTION_ROUTE, exact: false,
- }))
+ if (networkDropdownOpen === false) {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Home',
+ name: 'Opened Network Menu',
+ },
+ })
+ showNetworkDropdown()
+ } else {
+ hideNetworkDropdown()
+ }
}
renderAccountMenu () {
- const { isUnlocked, toggleAccountMenu, selectedAddress } = this.props
+ const { isUnlocked, toggleAccountMenu, selectedAddress, disabled, isAccountMenuOpen } = this.props
return isUnlocked && (
<div
className={classnames('account-menu__icon', {
- 'account-menu__icon--disabled': this.isConfirming(),
+ 'account-menu__icon--disabled': disabled,
})}
- onClick={() => this.isConfirming() || toggleAccountMenu()}
+ onClick={() => {
+ if (!disabled) {
+ !isAccountMenuOpen && this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Home',
+ name: 'Opened Main Menu',
+ },
+ })
+ toggleAccountMenu()
+ }
+ }}
>
<Identicon
address={selectedAddress}
@@ -66,38 +75,16 @@ export default class AppHeader extends PureComponent {
)
}
- hideAppHeader () {
- const { location } = this.props
-
- const isInitializing = Boolean(matchPath(location.pathname, {
- path: INITIALIZE_ROUTE, exact: false,
- }))
-
- if (isInitializing) {
- return true
- }
-
- if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) {
- return true
- }
-
- if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_POPUP && this.isConfirming()) {
- return true
- }
- }
-
render () {
const {
+ history,
network,
provider,
- history,
isUnlocked,
+ hideNetworkIndicator,
+ disabled,
} = this.props
- if (this.hideAppHeader()) {
- return null
- }
-
return (
<div
className={classnames('app-header', { 'app-header--back-drop': isUnlocked })}>
@@ -108,7 +95,7 @@ export default class AppHeader extends PureComponent {
>
<img
className="app-header__metafox-logo app-header__metafox-logo--horizontal"
- src="/images/logo/metamask-logo-horizontal-beta.svg"
+ src="/images/logo/metamask-logo-horizontal.svg"
height={30}
/>
<img
@@ -119,14 +106,18 @@ export default class AppHeader extends PureComponent {
/>
</div>
<div className="app-header__account-menu-container">
- <div className="app-header__network-component-wrapper">
- <NetworkIndicator
- network={network}
- provider={provider}
- onClick={event => this.handleNetworkIndicatorClick(event)}
- disabled={this.isConfirming()}
- />
- </div>
+ {
+ !hideNetworkIndicator && (
+ <div className="app-header__network-component-wrapper">
+ <NetworkIndicator
+ network={network}
+ provider={provider}
+ onClick={event => this.handleNetworkIndicatorClick(event)}
+ disabled={disabled}
+ />
+ </div>
+ )
+ }
{ this.renderAccountMenu() }
</div>
</div>
diff --git a/ui/app/components/app-header/app-header.container.js b/ui/app/components/app/app-header/app-header.container.js
index 30d3f8cc4..b67338245 100644
--- a/ui/app/components/app-header/app-header.container.js
+++ b/ui/app/components/app/app-header/app-header.container.js
@@ -3,7 +3,7 @@ import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
import AppHeader from './app-header.component'
-const actions = require('../../actions')
+const actions = require('../../../store/actions')
const mapStateToProps = state => {
const { appState, metamask } = state
@@ -13,6 +13,7 @@ const mapStateToProps = state => {
provider,
selectedAddress,
isUnlocked,
+ isAccountMenuOpen,
} = metamask
return {
@@ -21,6 +22,7 @@ const mapStateToProps = state => {
provider,
selectedAddress,
isUnlocked,
+ isAccountMenuOpen,
}
}
diff --git a/ui/app/components/app-header/index.js b/ui/app/components/app/app-header/index.js
index 6de2f9c78..6de2f9c78 100644
--- a/ui/app/components/app-header/index.js
+++ b/ui/app/components/app/app-header/index.js
diff --git a/ui/app/components/app-header/index.scss b/ui/app/components/app/app-header/index.scss
index 325844af5..325844af5 100644
--- a/ui/app/components/app-header/index.scss
+++ b/ui/app/components/app/app-header/index.scss
diff --git a/ui/app/components/bn-as-decimal-input.js b/ui/app/components/app/bn-as-decimal-input.js
index 9a033f893..9a033f893 100644
--- a/ui/app/components/bn-as-decimal-input.js
+++ b/ui/app/components/app/bn-as-decimal-input.js
diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/app/coinbase-form.js
index d5915292e..24d287604 100644
--- a/ui/app/components/coinbase-form.js
+++ b/ui/app/components/app/coinbase-form.js
@@ -3,7 +3,7 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const actions = require('../actions')
+const actions = require('../../store/actions')
CoinbaseForm.contextTypes = {
t: PropTypes.func,
diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js b/ui/app/components/app/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js
index c7262d2a9..18571eccb 100644
--- a/ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js
+++ b/ui/app/components/app/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js
@@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
-import { PRIMARY, SECONDARY } from '../../../constants/common'
+import { PRIMARY, SECONDARY } from '../../../../helpers/constants/common'
const ConfirmDetailRow = props => {
const {
diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/index.js b/ui/app/components/app/confirm-page-container/confirm-detail-row/index.js
index 056afff04..056afff04 100644
--- a/ui/app/components/confirm-page-container/confirm-detail-row/index.js
+++ b/ui/app/components/app/confirm-page-container/confirm-detail-row/index.js
diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/index.scss b/ui/app/components/app/confirm-page-container/confirm-detail-row/index.scss
index 580a41fde..1672ef8c6 100644
--- a/ui/app/components/confirm-page-container/confirm-detail-row/index.scss
+++ b/ui/app/components/app/confirm-page-container/confirm-detail-row/index.scss
@@ -43,4 +43,8 @@
font-size: .625rem;
}
}
+
+ .advanced-gas-inputs__gas-edit-rows {
+ margin-bottom: 16px;
+ }
}
diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js b/ui/app/components/app/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js
index c8507985d..c8507985d 100644
--- a/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js
+++ b/ui/app/components/app/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
index 1dca81560..8a5f90c76 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
@@ -1,9 +1,9 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-import { Tabs, Tab } from '../../tabs'
-import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from './'
-import ErrorMessage from '../../error-message'
+import { Tabs, Tab } from '../../../ui/tabs'
+import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.'
+import ErrorMessage from '../../../ui/error-message'
export default class ConfirmPageContainerContent extends Component {
static propTypes = {
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
index 89ceb015f..0cc4d8262 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-import Identicon from '../../../identicon'
+import Identicon from '../../../../ui/identicon'
const ConfirmPageContainerSummary = props => {
const {
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.js b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.js
index ed1b28cf2..ed1b28cf2 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.js
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.js
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss
index 7f0f5d37a..7f0f5d37a 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/confirm-page-container-warning.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/confirm-page-container-warning.component.js
index 79901c8fc..79901c8fc 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/confirm-page-container-warning.component.js
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/confirm-page-container-warning.component.js
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.js b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.js
index 6e48bd144..6e48bd144 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.js
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.js
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.scss b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.scss
index 50545a1a2..50545a1a2 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.scss
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.scss
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/index.js b/ui/app/components/app/confirm-page-container/confirm-page-container-content/index.js
index 4dfd89d92..4dfd89d92 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/index.js
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/index.js
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss b/ui/app/components/app/confirm-page-container/confirm-page-container-content/index.scss
index 698e624f4..602a46848 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/index.scss
@@ -1,6 +1,6 @@
-@import './confirm-page-container-warning/index';
+@import 'confirm-page-container-warning/index';
-@import './confirm-page-container-summary/index';
+@import 'confirm-page-container-summary/index';
.confirm-page-container-content {
overflow-y: auto;
@@ -52,6 +52,10 @@
&__gas-fee {
border-bottom: 1px solid $geyser;
+
+ .advanced-gas-inputs__gas-edit-rows {
+ margin-bottom: 16px;
+ }
}
&__function-type {
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js
index e6fe8f82c..84ca40da5 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
-} from '../../../../../app/scripts/lib/enums'
+} from '../../../../../../app/scripts/lib/enums'
import NetworkDisplay from '../../network-display'
export default class ConfirmPageContainer extends Component {
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-header/index.js b/ui/app/components/app/confirm-page-container/confirm-page-container-header/index.js
index 71feb6931..71feb6931 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-header/index.js
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-header/index.js
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-header/index.scss b/ui/app/components/app/confirm-page-container/confirm-page-container-header/index.scss
index 43e1e4427..be77edbdf 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-header/index.scss
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-header/index.scss
@@ -7,7 +7,7 @@
display: flex;
justify-content: space-between;
border-bottom: 1px solid $geyser;
- padding: 13px 13px 13px 24px;
+ padding: 4px 13px 4px 13px;
flex: 0 0 auto;
}
diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js
new file mode 100755
index 000000000..8327f997b
--- /dev/null
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js
@@ -0,0 +1,69 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+const ConfirmPageContainerNavigation = props => {
+ const { onNextTx, totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = props
+
+ return (
+ <div className="confirm-page-container-navigation"
+ style={{
+ display: showNavigation ? 'flex' : 'none',
+ }}
+ >
+ <div className="confirm-page-container-navigation__container"
+ style={{
+ visibility: prevTxId ? 'initial' : 'hidden',
+ }}>
+ <div
+ className="confirm-page-container-navigation__arrow"
+ onClick={() => onNextTx(firstTx)}>
+ <img src="/images/double-arrow.svg" />
+ </div>
+ <div
+ className="confirm-page-container-navigation__arrow"
+ onClick={() => onNextTx(prevTxId)}>
+ <img src="/images/single-arrow.svg" />
+ </div>
+ </div>
+ <div className="confirm-page-container-navigation__textcontainer">
+ <div className="confirm-page-container-navigation__navtext">
+ {positionOfCurrentTx} {ofText} {totalTx}
+ </div>
+ <div className="confirm-page-container-navigation__longtext">
+ {requestsWaitingText}
+ </div>
+ </div>
+ <div
+ className="confirm-page-container-navigation__container"
+ style={{
+ visibility: nextTxId ? 'initial' : 'hidden',
+ }}>
+ <div
+ className="confirm-page-container-navigation__arrow"
+ onClick={() => onNextTx(nextTxId)}>
+ <img className="confirm-page-container-navigation__imageflip" src="/images/single-arrow.svg" />
+ </div>
+ <div
+ className="confirm-page-container-navigation__arrow"
+ onClick={() => onNextTx(lastTx)}>
+ <img className="confirm-page-container-navigation__imageflip" src="/images/double-arrow.svg" />
+ </div>
+ </div>
+ </div>
+ )
+}
+
+ConfirmPageContainerNavigation.propTypes = {
+ totalTx: PropTypes.number,
+ positionOfCurrentTx: PropTypes.number,
+ onNextTx: PropTypes.func,
+ nextTxId: PropTypes.string,
+ prevTxId: PropTypes.string,
+ showNavigation: PropTypes.bool,
+ firstTx: PropTypes.string,
+ lastTx: PropTypes.string,
+ ofText: PropTypes.string,
+ requestsWaitingText: PropTypes.string,
+}
+
+export default ConfirmPageContainerNavigation
diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/index.js b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/index.js
new file mode 100755
index 000000000..d97c1b447
--- /dev/null
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-page-container-navigation.component'
diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/index.scss b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/index.scss
new file mode 100755
index 000000000..0cf184c60
--- /dev/null
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/index.scss
@@ -0,0 +1,54 @@
+.confirm-page-container-navigation {
+ display: flex;
+ justify-content: space-between;
+ font: inherit;
+ padding: 4px 10px 4px 10px;
+ border-bottom: 1px solid $geyser;
+ flex: 0 0 auto;
+
+ &__container {
+ display: flex;
+ }
+
+ &__arrow {
+ cursor: pointer;
+ display: flex;
+ padding-left: 5px;
+ padding-right: 5px;
+ }
+
+ &__arrow:hover {
+ -webkit-transform: scale(1.1);
+ -moz-transform: scale(1.1);
+ -o-transform: scale(1.1);
+ transform: scale(1.1);
+ }
+
+ &__arrow:active {
+ -webkit-transform: scale(0.95);
+ -moz-transform: scale(0.95);
+ -o-transform: scale(0.95);
+ transform: scale(0.95);
+ }
+
+ &__textcontainer {
+ text-align: center;
+ }
+
+ &__navtext {
+ font-size: 9px;
+ font-weight: bold;
+ }
+
+ &__longtext {
+ color: $oslo-gray;
+ font-size: 8px;
+ }
+
+ &__imageflip {
+ -webkit-transform: scaleX(-1);
+ -moz-transform: scaleX(-1);
+ -o-transform: scaleX(-1);
+ transform: scaleX(-1);
+ }
+} \ No newline at end of file
diff --git a/ui/app/components/confirm-page-container/confirm-page-container.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js
index 8b2e47cbb..326e4f83e 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container.component.js
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js
@@ -1,8 +1,8 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import SenderToRecipient from '../sender-to-recipient'
-import { PageContainerFooter } from '../page-container'
-import { ConfirmPageContainerHeader, ConfirmPageContainerContent } from './'
+import SenderToRecipient from '../../ui/sender-to-recipient'
+import { PageContainerFooter } from '../../ui/page-container'
+import { ConfirmPageContainerHeader, ConfirmPageContainerContent, ConfirmPageContainerNavigation } from '.'
export default class ConfirmPageContainer extends Component {
static contextTypes = {
@@ -43,6 +43,17 @@ export default class ConfirmPageContainer extends Component {
summaryComponent: PropTypes.node,
warning: PropTypes.string,
unapprovedTxCount: PropTypes.number,
+ // Navigation
+ totalTx: PropTypes.number,
+ positionOfCurrentTx: PropTypes.number,
+ nextTxId: PropTypes.string,
+ prevTxId: PropTypes.string,
+ showNavigation: PropTypes.bool,
+ onNextTx: PropTypes.func,
+ firstTx: PropTypes.string,
+ lastTx: PropTypes.string,
+ ofText: PropTypes.string,
+ requestsWaitingText: PropTypes.string,
// Footer
onCancelAll: PropTypes.func,
onCancel: PropTypes.func,
@@ -79,11 +90,33 @@ export default class ConfirmPageContainer extends Component {
unapprovedTxCount,
assetImage,
warning,
+ totalTx,
+ positionOfCurrentTx,
+ nextTxId,
+ prevTxId,
+ showNavigation,
+ onNextTx,
+ firstTx,
+ lastTx,
+ ofText,
+ requestsWaitingText,
} = this.props
const renderAssetImage = contentComponent || (!contentComponent && !identiconAddress)
return (
<div className="page-container">
+ <ConfirmPageContainerNavigation
+ totalTx={totalTx}
+ positionOfCurrentTx={positionOfCurrentTx}
+ nextTxId={nextTxId}
+ prevTxId={prevTxId}
+ showNavigation={showNavigation}
+ onNextTx={(txId) => onNextTx(txId)}
+ firstTx={firstTx}
+ lastTx={lastTx}
+ ofText={ofText}
+ requestsWaitingText={requestsWaitingText}
+ />
<ConfirmPageContainerHeader
showEdit={showEdit}
onEdit={() => onEdit()}
diff --git a/ui/app/components/confirm-page-container/index.js b/ui/app/components/app/confirm-page-container/index.js
index ee88aa5d3..28b17614e 100644
--- a/ui/app/components/confirm-page-container/index.js
+++ b/ui/app/components/app/confirm-page-container/index.js
@@ -1,6 +1,8 @@
export { default } from './confirm-page-container.component'
export { default as ConfirmPageContainerHeader } from './confirm-page-container-header'
export { default as ConfirmDetailRow } from './confirm-detail-row'
+export { default as ConfirmPageContainerNavigation } from './confirm-page-container-navigation'
+
export {
default as ConfirmPageContainerContent,
ConfirmPageContainerSummary,
diff --git a/ui/app/components/app/confirm-page-container/index.scss b/ui/app/components/app/confirm-page-container/index.scss
new file mode 100644
index 000000000..c0277eff5
--- /dev/null
+++ b/ui/app/components/app/confirm-page-container/index.scss
@@ -0,0 +1,7 @@
+@import 'confirm-page-container-content/index';
+
+@import 'confirm-page-container-header/index';
+
+@import 'confirm-detail-row/index';
+
+@import 'confirm-page-container-navigation/index';
diff --git a/ui/app/components/copyable.js b/ui/app/components/app/copyable.js
index ad504deb8..6869d674d 100644
--- a/ui/app/components/copyable.js
+++ b/ui/app/components/app/copyable.js
@@ -3,7 +3,7 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
-const Tooltip = require('./tooltip')
+const Tooltip = require('../ui/tooltip')
const copyToClipboard = require('copy-to-clipboard')
const connect = require('react-redux').connect
diff --git a/ui/app/components/customize-gas-modal/gas-modal-card.js b/ui/app/components/app/customize-gas-modal/gas-modal-card.js
index 23754d819..23754d819 100644
--- a/ui/app/components/customize-gas-modal/gas-modal-card.js
+++ b/ui/app/components/app/customize-gas-modal/gas-modal-card.js
diff --git a/ui/app/components/customize-gas-modal/gas-slider.js b/ui/app/components/app/customize-gas-modal/gas-slider.js
index 69fd6f985..69fd6f985 100644
--- a/ui/app/components/customize-gas-modal/gas-slider.js
+++ b/ui/app/components/app/customize-gas-modal/gas-slider.js
diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/app/customize-gas-modal/index.js
index e67fbe45b..dca77bb00 100644
--- a/ui/app/components/customize-gas-modal/index.js
+++ b/ui/app/components/app/customize-gas-modal/index.js
@@ -3,15 +3,16 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const actions = require('../../actions')
+const BigNumber = require('bignumber.js')
+const actions = require('../../../store/actions')
const GasModalCard = require('./gas-modal-card')
-import Button from '../button'
+import Button from '../../ui/button'
const ethUtil = require('ethereumjs-util')
import {
updateSendErrors,
-} from '../../ducks/send.duck'
+} from '../../../ducks/send/send.duck'
const {
MIN_GAS_PRICE_DEC,
@@ -29,7 +30,7 @@ const {
conversionGreaterThan,
conversionMax,
subtractCurrencies,
-} = require('../../conversion-util')
+} = require('../../../helpers/utils/conversion-util')
const {
getGasIsLoading,
@@ -41,7 +42,7 @@ const {
getCurrentAccountWithSendEtherInfo,
getSelectedTokenToFiatRate,
getSendMaxModeState,
-} = require('../../selectors')
+} = require('../../../selectors/selectors')
const {
getGasPrice,
@@ -112,6 +113,7 @@ function CustomizeGasModal (props) {
CustomizeGasModal.contextTypes = {
t: PropTypes.func,
+ metricsEvent: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal)
@@ -148,6 +150,7 @@ CustomizeGasModal.prototype.componentWillReceiveProps = function (nextProps) {
}
CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
+ const { metricsEvent } = this.context
const {
setGasPrice,
setGasLimit,
@@ -159,6 +162,9 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
updateSendAmount,
updateSendErrors,
} = this.props
+ const {
+ originalState,
+ } = this.state
if (maxModeOn && !selectedToken) {
const maxAmount = subtractCurrencies(
@@ -169,6 +175,22 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
updateSendAmount(maxAmount)
}
+ metricsEvent({
+ eventOpts: {
+ category: 'Activation',
+ action: 'userCloses',
+ name: 'closeCustomizeGas',
+ },
+ pageOpts: {
+ section: 'customizeGasModal',
+ component: 'customizeGasSaveButton',
+ },
+ customVariables: {
+ gasPriceChange: (new BigNumber(ethUtil.addHexPrefix(gasPrice))).minus(new BigNumber(ethUtil.addHexPrefix(originalState.gasPrice))).toString(10),
+ gasLimitChange: (new BigNumber(ethUtil.addHexPrefix(gasLimit))).minus(new BigNumber(ethUtil.addHexPrefix(originalState.gasLimit))).toString(10),
+ },
+ })
+
setGasPrice(ethUtil.addHexPrefix(gasPrice))
setGasLimit(ethUtil.addHexPrefix(gasLimit))
setGasTotal(ethUtil.addHexPrefix(gasTotal))
diff --git a/ui/app/components/dropdowns/account-details-dropdown.js b/ui/app/components/app/dropdowns/account-details-dropdown.js
index 7476cfdd9..3d4598946 100644
--- a/ui/app/components/dropdowns/account-details-dropdown.js
+++ b/ui/app/components/app/dropdowns/account-details-dropdown.js
@@ -3,13 +3,14 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const actions = require('../../actions')
-const { getSelectedIdentity } = require('../../selectors')
-const genAccountLink = require('../../../lib/account-link.js')
+const actions = require('../../../store/actions')
+const { getSelectedIdentity } = require('../../../selectors/selectors')
+const genAccountLink = require('../../../../lib/account-link.js')
const { Menu, Item, CloseArea } = require('./components/menu')
AccountDetailsDropdown.contextTypes = {
t: PropTypes.func,
+ metricsEvent: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailsDropdown)
@@ -72,6 +73,13 @@ AccountDetailsDropdown.prototype.render = function () {
h(Item, {
onClick: (e) => {
e.stopPropagation()
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Account Options',
+ name: 'Clicked Expand View',
+ },
+ })
global.platform.openExtensionInBrowser()
this.props.onClose()
},
@@ -82,6 +90,13 @@ AccountDetailsDropdown.prototype.render = function () {
onClick: (e) => {
e.stopPropagation()
showAccountDetailModal()
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Account Options',
+ name: 'Viewed Account Details',
+ },
+ })
this.props.onClose()
},
text: this.context.t('accountDetails'),
@@ -90,6 +105,13 @@ AccountDetailsDropdown.prototype.render = function () {
h(Item, {
onClick: (e) => {
e.stopPropagation()
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Account Options',
+ name: 'Clicked View on Etherscan',
+ },
+ })
viewOnEtherscan(address, network)
this.props.onClose()
},
diff --git a/ui/app/components/dropdowns/components/account-dropdowns.js b/ui/app/components/app/dropdowns/components/account-dropdowns.js
index e6b3e0c0c..c603a9a9f 100644
--- a/ui/app/components/dropdowns/components/account-dropdowns.js
+++ b/ui/app/components/app/dropdowns/components/account-dropdowns.js
@@ -1,15 +1,15 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
-const actions = require('../../../actions')
-const genAccountLink = require('../../../../lib/account-link.js')
+const actions = require('../../../../store/actions')
+const genAccountLink = require('../../../../../lib/account-link.js')
const connect = require('react-redux').connect
const Dropdown = require('./dropdown').Dropdown
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
-import Identicon from '../../identicon'
-const { checksumAddress } = require('../../../util')
+import Identicon from '../../../ui/identicon'
+const { checksumAddress } = require('../../../../helpers/utils/util')
const copyToClipboard = require('copy-to-clipboard')
-const { formatBalance } = require('../../../util')
+const { formatBalance } = require('../../../../helpers/utils/util')
class AccountDropdowns extends Component {
diff --git a/ui/app/components/dropdowns/components/dropdown.js b/ui/app/components/app/dropdowns/components/dropdown.js
index 149f063a7..149f063a7 100644
--- a/ui/app/components/dropdowns/components/dropdown.js
+++ b/ui/app/components/app/dropdowns/components/dropdown.js
diff --git a/ui/app/components/dropdowns/components/menu.js b/ui/app/components/app/dropdowns/components/menu.js
index f6d8a139e..f6d8a139e 100644
--- a/ui/app/components/dropdowns/components/menu.js
+++ b/ui/app/components/app/dropdowns/components/menu.js
diff --git a/ui/app/components/app/dropdowns/components/network-dropdown-icon.js b/ui/app/components/app/dropdowns/components/network-dropdown-icon.js
new file mode 100644
index 000000000..d4a2c2ff7
--- /dev/null
+++ b/ui/app/components/app/dropdowns/components/network-dropdown-icon.js
@@ -0,0 +1,47 @@
+const inherits = require('util').inherits
+const Component = require('react').Component
+const h = require('react-hyperscript')
+
+
+inherits(NetworkDropdownIcon, Component)
+module.exports = NetworkDropdownIcon
+
+function NetworkDropdownIcon () {
+ Component.call(this)
+}
+
+NetworkDropdownIcon.prototype.render = function () {
+ const {
+ backgroundColor,
+ isSelected,
+ innerBorder = 'none',
+ diameter = '12',
+ loading,
+ } = this.props
+
+ return loading
+ ? h('span.pointer.network-indicator', {
+ style: {
+ display: 'flex',
+ alignItems: 'center',
+ flexDirection: 'row',
+ },
+ }, [
+ h('img', {
+ style: {
+ width: '27px',
+ },
+ src: 'images/loading.svg',
+ }),
+ ])
+ : h(`.menu-icon-circle${isSelected ? '--active' : ''}`, {},
+ h('div', {
+ style: {
+ background: backgroundColor,
+ border: innerBorder,
+ height: `${diameter}px`,
+ width: `${diameter}px`,
+ },
+ })
+ )
+}
diff --git a/ui/app/components/dropdowns/index.js b/ui/app/components/app/dropdowns/index.js
index 605507058..605507058 100644
--- a/ui/app/components/dropdowns/index.js
+++ b/ui/app/components/app/dropdowns/index.js
diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/app/dropdowns/network-dropdown.js
index d4cc695a6..3d9037a06 100644
--- a/ui/app/components/dropdowns/network-dropdown.js
+++ b/ui/app/components/app/dropdowns/network-dropdown.js
@@ -5,12 +5,12 @@ const inherits = require('util').inherits
const connect = require('react-redux').connect
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
-const actions = require('../../actions')
+const actions = require('../../../store/actions')
const Dropdown = require('./components/dropdown').Dropdown
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
const NetworkDropdownIcon = require('./components/network-dropdown-icon')
const R = require('ramda')
-const { SETTINGS_ROUTE } = require('../../routes')
+const { ADVANCED_ROUTE } = require('../../../helpers/constants/routes')
// classes from nodes of the toggle element.
const notToggleElementClassnames = [
@@ -60,6 +60,7 @@ function NetworkDropdown () {
NetworkDropdown.contextTypes = {
t: PropTypes.func,
+ metricsEvent: PropTypes.func,
}
module.exports = compose(
@@ -120,7 +121,7 @@ NetworkDropdown.prototype.render = function () {
{
key: 'main',
closeMenu: () => this.props.hideNetworkDropdown(),
- onClick: () => props.setProviderType('mainnet'),
+ onClick: () => this.handleClick('mainnet'),
style: { ...dropdownMenuItemStyle, borderColor: '#038789' },
},
[
@@ -142,7 +143,7 @@ NetworkDropdown.prototype.render = function () {
{
key: 'ropsten',
closeMenu: () => this.props.hideNetworkDropdown(),
- onClick: () => props.setProviderType('ropsten'),
+ onClick: () => this.handleClick('ropsten'),
style: dropdownMenuItemStyle,
},
[
@@ -164,7 +165,7 @@ NetworkDropdown.prototype.render = function () {
{
key: 'kovan',
closeMenu: () => this.props.hideNetworkDropdown(),
- onClick: () => props.setProviderType('kovan'),
+ onClick: () => this.handleClick('kovan'),
style: dropdownMenuItemStyle,
},
[
@@ -186,7 +187,7 @@ NetworkDropdown.prototype.render = function () {
{
key: 'rinkeby',
closeMenu: () => this.props.hideNetworkDropdown(),
- onClick: () => props.setProviderType('rinkeby'),
+ onClick: () => this.handleClick('rinkeby'),
style: dropdownMenuItemStyle,
},
[
@@ -208,7 +209,7 @@ NetworkDropdown.prototype.render = function () {
{
key: 'default',
closeMenu: () => this.props.hideNetworkDropdown(),
- onClick: () => props.setProviderType('localhost'),
+ onClick: () => this.handleClick('localhost'),
style: dropdownMenuItemStyle,
},
[
@@ -232,7 +233,7 @@ NetworkDropdown.prototype.render = function () {
DropdownMenuItem,
{
closeMenu: () => this.props.hideNetworkDropdown(),
- onClick: () => this.props.history.push(SETTINGS_ROUTE),
+ onClick: () => this.props.history.push(ADVANCED_ROUTE),
style: dropdownMenuItemStyle,
},
[
@@ -252,6 +253,23 @@ NetworkDropdown.prototype.render = function () {
])
}
+NetworkDropdown.prototype.handleClick = function (newProviderType) {
+ const { provider: { type: providerType }, setProviderType } = this.props
+ const { metricsEvent } = this.context
+
+ metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Home',
+ name: 'Switched Networks',
+ },
+ customVariables: {
+ fromNetwork: providerType,
+ toNetwork: newProviderType,
+ },
+ })
+ setProviderType(newProviderType)
+}
NetworkDropdown.prototype.getNetworkName = function () {
const { provider } = this.props
@@ -277,7 +295,6 @@ NetworkDropdown.prototype.getNetworkName = function () {
NetworkDropdown.prototype.renderCommonRpc = function (rpcListDetail, provider) {
const props = this.props
const reversedRpcListDetail = rpcListDetail.slice().reverse()
- const network = props.network
return reversedRpcListDetail.map((entry) => {
const rpc = entry.rpcUrl
@@ -288,7 +305,7 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcListDetail, provider) {
if ((rpc === 'http://localhost:8545') || currentRpcTarget) {
return null
} else {
- const chainId = entry.chainId || network
+ const chainId = entry.chainId
return h(
DropdownMenuItem,
{
diff --git a/ui/app/components/dropdowns/simple-dropdown.js b/ui/app/components/app/dropdowns/simple-dropdown.js
index bba088ed1..bba088ed1 100644
--- a/ui/app/components/dropdowns/simple-dropdown.js
+++ b/ui/app/components/app/dropdowns/simple-dropdown.js
diff --git a/ui/app/components/dropdowns/tests/dropdown.test.js b/ui/app/components/app/dropdowns/tests/dropdown.test.js
index 2b026589a..2b026589a 100644
--- a/ui/app/components/dropdowns/tests/dropdown.test.js
+++ b/ui/app/components/app/dropdowns/tests/dropdown.test.js
diff --git a/ui/app/components/dropdowns/tests/menu.test.js b/ui/app/components/app/dropdowns/tests/menu.test.js
index 9f5f13f00..9f5f13f00 100644
--- a/ui/app/components/dropdowns/tests/menu.test.js
+++ b/ui/app/components/app/dropdowns/tests/menu.test.js
diff --git a/ui/app/components/dropdowns/tests/network-dropdown-icon.test.js b/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js
index 67b192c11..67b192c11 100644
--- a/ui/app/components/dropdowns/tests/network-dropdown-icon.test.js
+++ b/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js
diff --git a/ui/app/components/dropdowns/tests/network-dropdown.test.js b/ui/app/components/app/dropdowns/tests/network-dropdown.test.js
index 88ad56851..91e7899a7 100644
--- a/ui/app/components/dropdowns/tests/network-dropdown.test.js
+++ b/ui/app/components/app/dropdowns/tests/network-dropdown.test.js
@@ -1,7 +1,7 @@
import React from 'react'
import assert from 'assert'
import { createMockStore } from 'redux-test-utils'
-import { mountWithRouter } from '../../../../../test/lib/render-helpers'
+import { mountWithRouter } from '../../../../../../test/lib/render-helpers'
import NetworkDropdown from '../network-dropdown'
import { DropdownMenuItem } from '../components/dropdown'
import NetworkDropdownIcon from '../components/network-dropdown-icon'
diff --git a/ui/app/components/dropdowns/token-menu-dropdown.js b/ui/app/components/app/dropdowns/token-menu-dropdown.js
index 8a072b1bc..e2730aea2 100644
--- a/ui/app/components/dropdowns/token-menu-dropdown.js
+++ b/ui/app/components/app/dropdowns/token-menu-dropdown.js
@@ -3,7 +3,7 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const actions = require('../../actions')
+const actions = require('../../../store/actions')
const genAccountLink = require('etherscan-link').createAccountLink
const { Menu, Item, CloseArea } = require('./components/menu')
diff --git a/ui/app/components/ens-input.js b/ui/app/components/app/ens-input.js
index f538fd555..274058a1b 100644
--- a/ui/app/components/ens-input.js
+++ b/ui/app/components/app/ens-input.js
@@ -12,7 +12,7 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const connect = require('react-redux').connect
const ToAutoComplete = require('./send/to-autocomplete').default
const log = require('loglevel')
-const { isValidENSAddress } = require('../util')
+const { isValidENSAddress } = require('../../helpers/utils/util')
EnsInput.contextTypes = {
t: PropTypes.func,
@@ -128,7 +128,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 })
+ this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError, toWarning: state.toWarning })
}
}
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
new file mode 100644
index 000000000..95894140c
--- /dev/null
+++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js
@@ -0,0 +1,156 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import debounce from 'lodash.debounce'
+
+export default class AdvancedTabContent extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ updateCustomGasPrice: PropTypes.func,
+ updateCustomGasLimit: PropTypes.func,
+ customGasPrice: PropTypes.number,
+ customGasLimit: PropTypes.number,
+ insufficientBalance: PropTypes.bool,
+ customPriceIsSafe: PropTypes.bool,
+ isSpeedUp: PropTypes.bool,
+ showGasPriceInfoModal: PropTypes.func,
+ showGasLimitInfoModal: PropTypes.func,
+ }
+
+ debouncedGasLimitReset = debounce((dVal) => {
+ if (dVal < 21000) {
+ this.props.updateCustomGasLimit(21000)
+ }
+ }, 1000, { trailing: true })
+
+ onChangeGasLimit = (val) => {
+ this.props.updateCustomGasLimit(val)
+ this.debouncedGasLimitReset(val)
+ }
+
+ gasInputError ({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value }) {
+ const { t } = this.context
+ let errorText
+ let errorType
+ let isInError = true
+
+
+ if (insufficientBalance) {
+ errorText = t('insufficientBalance')
+ errorType = 'error'
+ } else if (labelKey === 'gasPrice' && isSpeedUp && value === 0) {
+ errorText = t('zeroGasPriceOnSpeedUpError')
+ errorType = 'error'
+ } else if (labelKey === 'gasPrice' && !customPriceIsSafe) {
+ errorText = t('gasPriceExtremelyLow')
+ errorType = 'warning'
+ } else {
+ isInError = false
+ }
+
+ return {
+ isInError,
+ errorText,
+ errorType,
+ }
+ }
+
+ gasInput ({ labelKey, value, onChange, insufficientBalance, showGWEI, customPriceIsSafe, isSpeedUp }) {
+ const {
+ isInError,
+ errorText,
+ errorType,
+ } = this.gasInputError({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value })
+
+ return (
+ <div className="advanced-gas-inputs__gas-edit-row__input-wrapper">
+ <input
+ className={classnames('advanced-gas-inputs__gas-edit-row__input', {
+ 'advanced-gas-inputs__gas-edit-row__input--error': isInError && errorType === 'error',
+ 'advanced-gas-inputs__gas-edit-row__input--warning': isInError && errorType === 'warning',
+ })}
+ type="number"
+ value={value}
+ onChange={event => onChange(Number(event.target.value))}
+ />
+ <div className={classnames('advanced-gas-inputs__gas-edit-row__input-arrows', {
+ 'advanced-gas-inputs__gas-edit-row__input--error': isInError && errorType === 'error',
+ 'advanced-gas-inputs__gas-edit-row__input--warning': isInError && errorType === 'warning',
+ })}>
+ <div
+ className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
+ onClick={() => onChange(value + 1)}
+ >
+ <i className="fa fa-sm fa-angle-up" />
+ </div>
+ <div
+ className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
+ onClick={() => onChange(Math.max(value - 1, 0))}
+ >
+ <i className="fa fa-sm fa-angle-down" />
+ </div>
+ </div>
+ { isInError
+ ? <div className={`advanced-gas-inputs__gas-edit-row__${errorType}-text`}>
+ { errorText }
+ </div>
+ : null }
+ </div>
+ )
+ }
+
+ infoButton (onClick) {
+ return <i className="fa fa-info-circle" onClick={onClick} />
+ }
+
+ renderGasEditRow (gasInputArgs) {
+ return (
+ <div className="advanced-gas-inputs__gas-edit-row">
+ <div className="advanced-gas-inputs__gas-edit-row__label">
+ { this.context.t(gasInputArgs.labelKey) }
+ { this.infoButton(() => gasInputArgs.infoOnClick()) }
+ </div>
+ { this.gasInput(gasInputArgs) }
+ </div>
+ )
+ }
+
+ render () {
+ const {
+ customGasPrice,
+ updateCustomGasPrice,
+ customGasLimit,
+ insufficientBalance,
+ customPriceIsSafe,
+ isSpeedUp,
+ showGasPriceInfoModal,
+ showGasLimitInfoModal,
+ } = this.props
+
+ return (
+ <div className="advanced-gas-inputs__gas-edit-rows">
+ { this.renderGasEditRow({
+ labelKey: 'gasPrice',
+ value: customGasPrice,
+ onChange: updateCustomGasPrice,
+ insufficientBalance,
+ customPriceIsSafe,
+ showGWEI: true,
+ isSpeedUp,
+ infoOnClick: showGasPriceInfoModal,
+ }) }
+ { this.renderGasEditRow({
+ labelKey: 'gasLimit',
+ value: customGasLimit,
+ onChange: this.onChangeGasLimit,
+ insufficientBalance,
+ customPriceIsSafe,
+ infoOnClick: showGasLimitInfoModal,
+ }) }
+ </div>
+ )
+ }
+}
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
new file mode 100644
index 000000000..90fef1a1b
--- /dev/null
+++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js
@@ -0,0 +1,38 @@
+import { connect } from 'react-redux'
+import { showModal } from '../../../../store/actions'
+import {
+ decGWEIToHexWEI,
+ decimalToHex,
+ hexWEIToDecGWEI,
+} from '../../../../helpers/utils/conversions.util'
+import AdvancedGasInputs from './advanced-gas-inputs.component'
+
+function convertGasPriceForInputs (gasPriceInHexWEI) {
+ return Number(hexWEIToDecGWEI(gasPriceInHexWEI))
+}
+
+function convertGasLimitForInputs (gasLimitInHexWEI) {
+ return parseInt(gasLimitInHexWEI, 16)
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ showGasPriceInfoModal: modalName => dispatch(showModal({ name: 'GAS_PRICE_INFO_MODAL' })),
+ showGasLimitInfoModal: modalName => dispatch(showModal({ name: 'GAS_LIMIT_INFO_MODAL' })),
+ }
+}
+
+const mergeProps = (stateProps, dispatchProps, ownProps) => {
+ const {customGasPrice, customGasLimit, updateCustomGasPrice, updateCustomGasLimit} = ownProps
+ return {
+ ...stateProps,
+ ...dispatchProps,
+ ...ownProps,
+ customGasPrice: convertGasPriceForInputs(customGasPrice),
+ customGasLimit: convertGasLimitForInputs(customGasLimit),
+ updateCustomGasPrice: (price) => updateCustomGasPrice(decGWEIToHexWEI(price)),
+ updateCustomGasLimit: (limit) => updateCustomGasLimit(decimalToHex(limit)),
+ }
+}
+
+export default connect(null, mapDispatchToProps, mergeProps)(AdvancedGasInputs)
diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/index.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/index.js
new file mode 100644
index 000000000..bd8abaa3e
--- /dev/null
+++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/index.js
@@ -0,0 +1 @@
+export { default } from './advanced-gas-inputs.container'
diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/index.scss b/ui/app/components/app/gas-customization/advanced-gas-inputs/index.scss
new file mode 100644
index 000000000..50953cbe5
--- /dev/null
+++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/index.scss
@@ -0,0 +1,133 @@
+.advanced-gas-inputs {
+ &__gas-edit-rows {
+ display: flex;
+ flex-flow: row;
+ justify-content: space-between;
+ }
+
+ &__gas-edit-row {
+ display: flex;
+ flex-flow: column;
+ width: 47.5%;
+
+ &__label {
+ color: #313B5E;
+ font-size: 12px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ @media screen and (max-width: 576px) {
+ font-size: 10px;
+ }
+
+ .fa-info-circle {
+ color: $silver;
+ margin-left: 10px;
+ cursor: pointer;
+ }
+
+ .fa-info-circle:hover {
+ color: $mid-gray;
+ }
+ }
+
+ &__error-text {
+ font-size: 12px;
+ color: red;
+ }
+
+ &__warning-text {
+ font-size: 12px;
+ color: orange;
+ }
+
+ &__input-wrapper {
+ position: relative;
+ }
+
+ &__input {
+ border: 1px solid $dusty-gray;
+ border-radius: 4px;
+ color: $mid-gray;
+ font-size: 16px;
+ height: 24px;
+ width: 100%;
+ padding-left: 8px;
+ padding-top: 2px;
+ margin-top: 7px;
+ }
+
+ &__input--error {
+ border: 1px solid $red;
+ }
+
+ &__input--warning {
+ border: 1px solid $orange;
+ }
+
+ &__input-arrows {
+ position: absolute;
+ top: 7px;
+ right: 0px;
+ width: 17px;
+ height: 24px;
+ border: 1px solid #dadada;
+ border-top-right-radius: 4px;
+ display: flex;
+ flex-direction: column;
+ color: #9b9b9b;
+ font-size: .8em;
+ border-bottom-right-radius: 4px;
+ cursor: pointer;
+
+ &__i-wrap {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ cursor: pointer;
+ }
+
+ &__i-wrap:hover {
+ background: #4EADE7;
+ color: $white;
+ }
+
+ i:hover {
+ background: #4EADE7;
+ }
+
+ i {
+ font-size: 10px;
+ }
+ }
+
+ &__input-arrows--error {
+ border: 1px solid $red;
+ }
+
+ &__input-arrows--warning {
+ border: 1px solid $orange;
+ }
+
+ input[type="number"]::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ display: none;
+ }
+
+ input[type="number"]:hover::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ display: none;
+ }
+
+ &__gwei-symbol {
+ position: absolute;
+ top: 8px;
+ right: 10px;
+ color: $dusty-gray;
+ }
+ }
+} \ No newline at end of file
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
new file mode 100644
index 000000000..ad8628621
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js
@@ -0,0 +1,226 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import Loading from '../../../../ui/loading-screen'
+import GasPriceChart from '../../gas-price-chart'
+import debounce from 'lodash.debounce'
+
+export default class AdvancedTabContent extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ updateCustomGasPrice: PropTypes.func,
+ updateCustomGasLimit: PropTypes.func,
+ customGasPrice: PropTypes.number,
+ customGasLimit: PropTypes.number,
+ gasEstimatesLoading: PropTypes.bool,
+ millisecondsRemaining: PropTypes.number,
+ transactionFee: PropTypes.string,
+ timeRemaining: PropTypes.string,
+ gasChartProps: PropTypes.object,
+ insufficientBalance: PropTypes.bool,
+ customPriceIsSafe: PropTypes.bool,
+ isSpeedUp: PropTypes.bool,
+ isEthereumNetwork: PropTypes.bool,
+ }
+
+ constructor (props) {
+ super(props)
+
+ this.debouncedGasLimitReset = debounce((dVal) => {
+ if (dVal < 21000) {
+ props.updateCustomGasLimit(21000)
+ }
+ }, 1000, { trailing: true })
+ this.onChangeGasLimit = (val) => {
+ props.updateCustomGasLimit(val)
+ this.debouncedGasLimitReset(val)
+ }
+ }
+
+ gasInputError ({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value }) {
+ const { t } = this.context
+ let errorText
+ let errorType
+ let isInError = true
+
+
+ if (insufficientBalance) {
+ errorText = t('insufficientBalance')
+ errorType = 'error'
+ } else if (labelKey === 'gasPrice' && isSpeedUp && value === 0) {
+ errorText = t('zeroGasPriceOnSpeedUpError')
+ errorType = 'error'
+ } else if (labelKey === 'gasPrice' && !customPriceIsSafe) {
+ errorText = t('gasPriceExtremelyLow')
+ errorType = 'warning'
+ } else {
+ isInError = false
+ }
+
+ return {
+ isInError,
+ errorText,
+ errorType,
+ }
+ }
+
+ gasInput ({ labelKey, value, onChange, insufficientBalance, showGWEI, customPriceIsSafe, isSpeedUp }) {
+ const {
+ isInError,
+ errorText,
+ errorType,
+ } = this.gasInputError({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value })
+
+ return (
+ <div className="advanced-tab__gas-edit-row__input-wrapper">
+ <input
+ className={classnames('advanced-tab__gas-edit-row__input', {
+ 'advanced-tab__gas-edit-row__input--error': isInError && errorType === 'error',
+ 'advanced-tab__gas-edit-row__input--warning': isInError && errorType === 'warning',
+ })}
+ type="number"
+ value={value}
+ onChange={event => onChange(Number(event.target.value))}
+ />
+ <div className={classnames('advanced-tab__gas-edit-row__input-arrows', {
+ 'advanced-tab__gas-edit-row__input--error': isInError && errorType === 'error',
+ 'advanced-tab__gas-edit-row__input--warning': isInError && errorType === 'warning',
+ })}>
+ <div
+ className="advanced-tab__gas-edit-row__input-arrows__i-wrap"
+ onClick={() => onChange(value + 1)}
+ >
+ <i className="fa fa-sm fa-angle-up" />
+ </div>
+ <div
+ className="advanced-tab__gas-edit-row__input-arrows__i-wrap"
+ onClick={() => onChange(Math.max(value - 1, 0))}
+ >
+ <i className="fa fa-sm fa-angle-down" />
+ </div>
+ </div>
+ { isInError
+ ? <div className={`advanced-tab__gas-edit-row__${errorType}-text`}>
+ { errorText }
+ </div>
+ : null }
+ </div>
+ )
+ }
+
+ infoButton (onClick) {
+ return <i className="fa fa-info-circle" onClick={onClick} />
+ }
+
+ renderDataSummary (transactionFee, timeRemaining) {
+ return (
+ <div className="advanced-tab__transaction-data-summary">
+ <div className="advanced-tab__transaction-data-summary__titles">
+ <span>{ this.context.t('newTransactionFee') }</span>
+ <span>~{ this.context.t('transactionTime') }</span>
+ </div>
+ <div className="advanced-tab__transaction-data-summary__container">
+ <div className="advanced-tab__transaction-data-summary__fee">
+ {transactionFee}
+ </div>
+ <div className="time-remaining">{timeRemaining}</div>
+ </div>
+ </div>
+ )
+ }
+
+ renderGasEditRow (gasInputArgs) {
+ return (
+ <div className="advanced-tab__gas-edit-row">
+ <div className="advanced-tab__gas-edit-row__label">
+ { this.context.t(gasInputArgs.labelKey) }
+ { this.infoButton(() => {}) }
+ </div>
+ { this.gasInput(gasInputArgs) }
+ </div>
+ )
+ }
+
+ renderGasEditRows ({
+ customGasPrice,
+ updateCustomGasPrice,
+ customGasLimit,
+ updateCustomGasLimit,
+ insufficientBalance,
+ customPriceIsSafe,
+ isSpeedUp,
+ }) {
+ return (
+ <div className="advanced-tab__gas-edit-rows">
+ { this.renderGasEditRow({
+ labelKey: 'gasPrice',
+ value: customGasPrice,
+ onChange: updateCustomGasPrice,
+ insufficientBalance,
+ customPriceIsSafe,
+ showGWEI: true,
+ isSpeedUp,
+ }) }
+ { this.renderGasEditRow({
+ labelKey: 'gasLimit',
+ value: customGasLimit,
+ onChange: this.onChangeGasLimit,
+ insufficientBalance,
+ customPriceIsSafe,
+ }) }
+ </div>
+ )
+ }
+
+ render () {
+ const { t } = this.context
+ const {
+ updateCustomGasPrice,
+ updateCustomGasLimit,
+ timeRemaining,
+ customGasPrice,
+ customGasLimit,
+ insufficientBalance,
+ gasChartProps,
+ gasEstimatesLoading,
+ customPriceIsSafe,
+ isSpeedUp,
+ transactionFee,
+ isEthereumNetwork,
+ } = this.props
+
+ return (
+ <div className="advanced-tab">
+ { this.renderDataSummary(transactionFee, timeRemaining) }
+ <div className="advanced-tab__fee-chart">
+ { this.renderGasEditRows({
+ customGasPrice,
+ updateCustomGasPrice,
+ customGasLimit,
+ updateCustomGasLimit,
+ insufficientBalance,
+ customPriceIsSafe,
+ isSpeedUp,
+ }) }
+ { isEthereumNetwork
+ ? <div>
+ <div className="advanced-tab__fee-chart__title">{ t('liveGasPricePredictions') }</div>
+ {!gasEstimatesLoading
+ ? <GasPriceChart {...gasChartProps} updateCustomGasPrice={updateCustomGasPrice} />
+ : <Loading />
+ }
+ <div className="advanced-tab__fee-chart__speed-buttons">
+ <span>{ t('slower') }</span>
+ <span>{ t('faster') }</span>
+ </div>
+ </div>
+ : <div className="advanced-tab__fee-chart__title">{ t('chartOnlyAvailableEth') }</div>
+ }
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.js
new file mode 100644
index 000000000..492037f25
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.js
@@ -0,0 +1 @@
+export { default } from './advanced-tab-content.component'
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss
new file mode 100644
index 000000000..20a503018
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss
@@ -0,0 +1,207 @@
+@import './time-remaining/index';
+
+.advanced-tab {
+ display: flex;
+ flex-flow: column;
+
+ &__transaction-data-summary,
+ &__fee-chart-title {
+ padding-left: 24px;
+ padding-right: 24px;
+ }
+
+ &__transaction-data-summary {
+ display: flex;
+ flex-flow: column;
+ color: $mid-gray;
+ margin-top: 12px;
+ padding-left: 18px;
+ padding-right: 18px;
+
+ &__titles,
+ &__container {
+ display: flex;
+ flex-flow: row;
+ justify-content: space-between;
+ font-size: 12px;
+ color: #888EA3;
+ }
+
+ &__container {
+ font-size: 16px;
+ margin-top: 0px;
+ }
+
+ &__fee {
+ font-size: 16px;
+ color: #313A5E;
+ }
+ }
+
+ &__fee-chart {
+ margin-top: 8px;
+ height: 265px;
+ background: #F8F9FB;
+ border-bottom: 1px solid #d2d8dd;
+ border-top: 1px solid #d2d8dd;
+ position: relative;
+
+ &__title {
+ font-size: 12px;
+ color: #313A5E;
+ margin-left: 22px;
+ }
+
+ &__speed-buttons {
+ position: absolute;
+ bottom: 13px;
+ display: flex;
+ justify-content: space-between;
+ padding-left: 20px;
+ padding-right: 19px;
+ width: 100%;
+ font-size: 10px;
+ color: #888EA3;
+ }
+
+ .loading-overlay {
+ height: auto;
+ }
+ }
+
+ &__slider-container {
+ padding-left: 27px;
+ padding-right: 27px;
+ }
+
+ &__gas-edit-rows {
+ height: 73px;
+ display: flex;
+ flex-flow: row;
+ justify-content: space-between;
+ margin-left: 20px;
+ margin-right: 10px;
+ margin-top: 9px;
+ }
+
+ &__gas-edit-row {
+ display: flex;
+ flex-flow: column;
+
+ &__label {
+ color: #313B5E;
+ font-size: 14px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .fa-info-circle {
+ color: $silver;
+ margin-left: 10px;
+ cursor: pointer;
+ }
+
+ .fa-info-circle:hover {
+ color: $mid-gray;
+ }
+ }
+
+ &__error-text {
+ font-size: 12px;
+ color: red;
+ }
+
+ &__warning-text {
+ font-size: 12px;
+ color: orange;
+ }
+
+ &__input-wrapper {
+ position: relative;
+ }
+
+ &__input {
+ border: 1px solid $dusty-gray;
+ border-radius: 4px;
+ color: $mid-gray;
+ font-size: 16px;
+ height: 24px;
+ width: 155px;
+ padding-left: 8px;
+ padding-top: 2px;
+ margin-top: 7px;
+ }
+
+ &__input--error {
+ border: 1px solid $red;
+ }
+
+ &__input--warning {
+ border: 1px solid $orange;
+ }
+
+ &__input-arrows {
+ position: absolute;
+ top: 7px;
+ right: 0px;
+ width: 17px;
+ height: 24px;
+ border: 1px solid #dadada;
+ border-top-right-radius: 4px;
+ display: flex;
+ flex-direction: column;
+ color: #9b9b9b;
+ font-size: .8em;
+ border-bottom-right-radius: 4px;
+ cursor: pointer;
+
+ &__i-wrap {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ cursor: pointer;
+ }
+
+ &__i-wrap:hover {
+ background: #4EADE7;
+ color: $white;
+ }
+
+ i:hover {
+ background: #4EADE7;
+ }
+
+ i {
+ font-size: 10px;
+ }
+ }
+
+ &__input-arrows--error {
+ border: 1px solid $red;
+ }
+
+ &__input-arrows--warning {
+ border: 1px solid $orange;
+ }
+
+ input[type="number"]::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ display: none;
+ }
+
+ input[type="number"]:hover::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ display: none;
+ }
+
+ &__gwei-symbol {
+ position: absolute;
+ top: 8px;
+ right: 10px;
+ color: $dusty-gray;
+ }
+ }
+} \ No newline at end of file
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js
new file mode 100644
index 000000000..5f7d90922
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js
@@ -0,0 +1,365 @@
+import React from 'react'
+import assert from 'assert'
+import shallow from '../../../../../../../lib/shallow-with-context'
+import sinon from 'sinon'
+import AdvancedTabContent from '../advanced-tab-content.component.js'
+
+import GasPriceChart from '../../../gas-price-chart'
+import Loading from '../../../../../ui/loading-screen'
+
+const propsMethodSpies = {
+ updateCustomGasPrice: sinon.spy(),
+ updateCustomGasLimit: sinon.spy(),
+}
+
+sinon.spy(AdvancedTabContent.prototype, 'renderGasEditRow')
+sinon.spy(AdvancedTabContent.prototype, 'gasInput')
+sinon.spy(AdvancedTabContent.prototype, 'renderGasEditRows')
+sinon.spy(AdvancedTabContent.prototype, 'renderDataSummary')
+sinon.spy(AdvancedTabContent.prototype, 'gasInputError')
+
+describe('AdvancedTabContent Component', function () {
+ let wrapper
+
+ beforeEach(() => {
+ wrapper = shallow(<AdvancedTabContent
+ updateCustomGasPrice={propsMethodSpies.updateCustomGasPrice}
+ updateCustomGasLimit={propsMethodSpies.updateCustomGasLimit}
+ customGasPrice={11}
+ customGasLimit={23456}
+ timeRemaining={21500}
+ transactionFee={'$0.25'}
+ insufficientBalance={false}
+ customPriceIsSafe={true}
+ isSpeedUp={false}
+ isEthereumNetwork={true}
+ />, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } })
+ })
+
+ afterEach(() => {
+ propsMethodSpies.updateCustomGasPrice.resetHistory()
+ propsMethodSpies.updateCustomGasLimit.resetHistory()
+ AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
+ AdvancedTabContent.prototype.gasInput.resetHistory()
+ AdvancedTabContent.prototype.renderGasEditRows.resetHistory()
+ AdvancedTabContent.prototype.renderDataSummary.resetHistory()
+ })
+
+ describe('render()', () => {
+ it('should render the advanced-tab root node', () => {
+ assert(wrapper.hasClass('advanced-tab'))
+ })
+
+ it('should render the expected four children of the advanced-tab div', () => {
+ const advancedTabChildren = wrapper.children()
+ assert.equal(advancedTabChildren.length, 2)
+
+ assert(advancedTabChildren.at(0).hasClass('advanced-tab__transaction-data-summary'))
+ assert(advancedTabChildren.at(1).hasClass('advanced-tab__fee-chart'))
+
+ const feeChartDiv = advancedTabChildren.at(1)
+
+ assert(feeChartDiv.childAt(0).hasClass('advanced-tab__gas-edit-rows'))
+ assert(feeChartDiv.childAt(1).childAt(0).hasClass('advanced-tab__fee-chart__title'))
+ assert(feeChartDiv.childAt(1).childAt(1).is(GasPriceChart))
+ assert(feeChartDiv.childAt(1).childAt(2).hasClass('advanced-tab__fee-chart__speed-buttons'))
+ })
+
+ it('should render a loading component instead of the chart if gasEstimatesLoading is true', () => {
+ wrapper.setProps({ gasEstimatesLoading: true })
+ const advancedTabChildren = wrapper.children()
+ assert.equal(advancedTabChildren.length, 2)
+
+ assert(advancedTabChildren.at(0).hasClass('advanced-tab__transaction-data-summary'))
+ assert(advancedTabChildren.at(1).hasClass('advanced-tab__fee-chart'))
+
+ const feeChartDiv = advancedTabChildren.at(1)
+
+ assert(feeChartDiv.childAt(0).hasClass('advanced-tab__gas-edit-rows'))
+ assert(feeChartDiv.childAt(1).childAt(0).hasClass('advanced-tab__fee-chart__title'))
+ assert(feeChartDiv.childAt(1).childAt(1).is(Loading))
+ assert(feeChartDiv.childAt(1).childAt(2).hasClass('advanced-tab__fee-chart__speed-buttons'))
+ })
+
+ it('should call renderDataSummary with the expected params', () => {
+ assert.equal(AdvancedTabContent.prototype.renderGasEditRows.callCount, 1)
+ const renderDataSummaryArgs = AdvancedTabContent.prototype.renderDataSummary.getCall(0).args
+ assert.deepEqual(renderDataSummaryArgs, ['$0.25', 21500])
+ })
+
+ it('should call renderGasEditRows with the expected params', () => {
+ assert.equal(AdvancedTabContent.prototype.renderGasEditRows.callCount, 1)
+ const renderGasEditRowArgs = AdvancedTabContent.prototype.renderGasEditRows.getCall(0).args
+ assert.deepEqual(renderGasEditRowArgs, [{
+ customGasPrice: 11,
+ customGasLimit: 23456,
+ insufficientBalance: false,
+ customPriceIsSafe: true,
+ updateCustomGasPrice: propsMethodSpies.updateCustomGasPrice,
+ updateCustomGasLimit: propsMethodSpies.updateCustomGasLimit,
+ isSpeedUp: false,
+ }])
+ })
+ })
+
+ describe('renderDataSummary()', () => {
+ let dataSummary
+
+ beforeEach(() => {
+ dataSummary = shallow(wrapper.instance().renderDataSummary('mockTotalFee', 'mockMsRemaining'))
+ })
+
+ it('should render the transaction-data-summary root node', () => {
+ assert(dataSummary.hasClass('advanced-tab__transaction-data-summary'))
+ })
+
+ it('should render titles of the data', () => {
+ const titlesNode = dataSummary.children().at(0)
+ assert(titlesNode.hasClass('advanced-tab__transaction-data-summary__titles'))
+ assert.equal(titlesNode.children().at(0).text(), 'newTransactionFee')
+ assert.equal(titlesNode.children().at(1).text(), '~transactionTime')
+ })
+
+ it('should render the data', () => {
+ const dataNode = dataSummary.children().at(1)
+ assert(dataNode.hasClass('advanced-tab__transaction-data-summary__container'))
+ assert.equal(dataNode.children().at(0).text(), 'mockTotalFee')
+ assert(dataNode.children().at(1).hasClass('time-remaining'))
+ assert.equal(dataNode.children().at(1).text(), 'mockMsRemaining')
+ })
+ })
+
+ describe('renderGasEditRow()', () => {
+ let gasEditRow
+
+ beforeEach(() => {
+ AdvancedTabContent.prototype.gasInput.resetHistory()
+ gasEditRow = shallow(wrapper.instance().renderGasEditRow({
+ labelKey: 'mockLabelKey',
+ someArg: 'argA',
+ }))
+ })
+
+ it('should render the gas-edit-row root node', () => {
+ assert(gasEditRow.hasClass('advanced-tab__gas-edit-row'))
+ })
+
+ it('should render a label and an input', () => {
+ const gasEditRowChildren = gasEditRow.children()
+ assert.equal(gasEditRowChildren.length, 2)
+ assert(gasEditRowChildren.at(0).hasClass('advanced-tab__gas-edit-row__label'))
+ assert(gasEditRowChildren.at(1).hasClass('advanced-tab__gas-edit-row__input-wrapper'))
+ })
+
+ it('should render the label key and info button', () => {
+ const gasRowLabelChildren = gasEditRow.children().at(0).children()
+ assert.equal(gasRowLabelChildren.length, 2)
+ assert(gasRowLabelChildren.at(0), 'mockLabelKey')
+ assert(gasRowLabelChildren.at(1).hasClass('fa-info-circle'))
+ })
+
+ it('should call this.gasInput with the correct args', () => {
+ const gasInputSpyArgs = AdvancedTabContent.prototype.gasInput.args
+ assert.deepEqual(gasInputSpyArgs[0], [ { labelKey: 'mockLabelKey', someArg: 'argA' } ])
+ })
+ })
+
+ describe('renderGasEditRows()', () => {
+ let gasEditRows
+ let tempOnChangeGasLimit
+
+ beforeEach(() => {
+ tempOnChangeGasLimit = wrapper.instance().onChangeGasLimit
+ wrapper.instance().onChangeGasLimit = () => 'mockOnChangeGasLimit'
+ AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
+ gasEditRows = shallow(wrapper.instance().renderGasEditRows(
+ 'mockGasPrice',
+ () => 'mockUpdateCustomGasPriceReturn',
+ 'mockGasLimit',
+ () => 'mockUpdateCustomGasLimitReturn',
+ false
+ ))
+ })
+
+ afterEach(() => {
+ wrapper.instance().onChangeGasLimit = tempOnChangeGasLimit
+ })
+
+ it('should render the gas-edit-rows root node', () => {
+ assert(gasEditRows.hasClass('advanced-tab__gas-edit-rows'))
+ })
+
+ it('should render two rows', () => {
+ const gasEditRowsChildren = gasEditRows.children()
+ assert.equal(gasEditRowsChildren.length, 2)
+ assert(gasEditRowsChildren.at(0).hasClass('advanced-tab__gas-edit-row'))
+ assert(gasEditRowsChildren.at(1).hasClass('advanced-tab__gas-edit-row'))
+ })
+
+ it('should call this.renderGasEditRow twice, with the expected args', () => {
+ const renderGasEditRowSpyArgs = AdvancedTabContent.prototype.renderGasEditRow.args
+ assert.equal(renderGasEditRowSpyArgs.length, 2)
+ assert.deepEqual(renderGasEditRowSpyArgs[0].map(String), [{
+ labelKey: 'gasPrice',
+ value: 'mockGasLimit',
+ onChange: () => 'mockOnChangeGasLimit',
+ insufficientBalance: false,
+ customPriceIsSafe: true,
+ showGWEI: true,
+ }].map(String))
+ assert.deepEqual(renderGasEditRowSpyArgs[1].map(String), [{
+ labelKey: 'gasPrice',
+ value: 'mockGasPrice',
+ onChange: () => 'mockUpdateCustomGasPriceReturn',
+ insufficientBalance: false,
+ customPriceIsSafe: true,
+ showGWEI: true,
+ }].map(String))
+ })
+ })
+
+ describe('infoButton()', () => {
+ let infoButton
+
+ beforeEach(() => {
+ AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
+ infoButton = shallow(wrapper.instance().infoButton(() => 'mockOnClickReturn'))
+ })
+
+ it('should render the i element', () => {
+ assert(infoButton.hasClass('fa-info-circle'))
+ })
+
+ it('should pass the onClick argument to the i tag onClick prop', () => {
+ assert(infoButton.props().onClick(), 'mockOnClickReturn')
+ })
+ })
+
+ describe('gasInput()', () => {
+ let gasInput
+
+ beforeEach(() => {
+ AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
+ AdvancedTabContent.prototype.gasInputError.resetHistory()
+ gasInput = shallow(wrapper.instance().gasInput({
+ labelKey: 'gasPrice',
+ value: 321,
+ onChange: value => value + 7,
+ insufficientBalance: false,
+ showGWEI: true,
+ customPriceIsSafe: true,
+ isSpeedUp: false,
+ }))
+ })
+
+ it('should render the input-wrapper root node', () => {
+ assert(gasInput.hasClass('advanced-tab__gas-edit-row__input-wrapper'))
+ })
+
+ it('should render two children, including an input', () => {
+ assert.equal(gasInput.children().length, 2)
+ assert(gasInput.children().at(0).hasClass('advanced-tab__gas-edit-row__input'))
+ })
+
+ it('should call the passed onChange method with the value of the input onChange event', () => {
+ const inputOnChange = gasInput.find('input').props().onChange
+ assert.equal(inputOnChange({ target: { value: 8} }), 15)
+ })
+
+ it('should have two input arrows', () => {
+ const upArrow = gasInput.find('.fa-angle-up')
+ assert.equal(upArrow.length, 1)
+ const downArrow = gasInput.find('.fa-angle-down')
+ assert.equal(downArrow.length, 1)
+ })
+
+ it('should call onChange with the value incremented decremented when its onchange method is called', () => {
+ const upArrow = gasInput.find('.advanced-tab__gas-edit-row__input-arrows__i-wrap').at(0)
+ assert.equal(upArrow.props().onClick(), 329)
+ const downArrow = gasInput.find('.advanced-tab__gas-edit-row__input-arrows__i-wrap').at(1)
+ assert.equal(downArrow.props().onClick(), 327)
+ })
+
+ it('should call gasInputError with the expected params', () => {
+ assert.equal(AdvancedTabContent.prototype.gasInputError.callCount, 1)
+ const gasInputErrorArgs = AdvancedTabContent.prototype.gasInputError.getCall(0).args
+ assert.deepEqual(gasInputErrorArgs, [{
+ labelKey: 'gasPrice',
+ insufficientBalance: false,
+ customPriceIsSafe: true,
+ value: 321,
+ isSpeedUp: false,
+ }])
+ })
+ })
+
+ describe('gasInputError()', () => {
+ let gasInputError
+
+ beforeEach(() => {
+ AdvancedTabContent.prototype.renderGasEditRow.resetHistory()
+ gasInputError = wrapper.instance().gasInputError({
+ labelKey: '',
+ insufficientBalance: false,
+ customPriceIsSafe: true,
+ isSpeedUp: false,
+ })
+ })
+
+ it('should return an insufficientBalance error', () => {
+ const gasInputError = wrapper.instance().gasInputError({
+ labelKey: 'gasPrice',
+ insufficientBalance: true,
+ customPriceIsSafe: true,
+ isSpeedUp: false,
+ value: 1,
+ })
+ assert.deepEqual(gasInputError, {
+ isInError: true,
+ errorText: 'insufficientBalance',
+ errorType: 'error',
+ })
+ })
+
+ it('should return a zero gas on retry error', () => {
+ const gasInputError = wrapper.instance().gasInputError({
+ labelKey: 'gasPrice',
+ insufficientBalance: false,
+ customPriceIsSafe: false,
+ isSpeedUp: true,
+ value: 0,
+ })
+ assert.deepEqual(gasInputError, {
+ isInError: true,
+ errorText: 'zeroGasPriceOnSpeedUpError',
+ errorType: 'error',
+ })
+ })
+
+ it('should return a low gas warning', () => {
+ const gasInputError = wrapper.instance().gasInputError({
+ labelKey: 'gasPrice',
+ insufficientBalance: false,
+ customPriceIsSafe: false,
+ isSpeedUp: false,
+ value: 1,
+ })
+ assert.deepEqual(gasInputError, {
+ isInError: true,
+ errorText: 'gasPriceExtremelyLow',
+ errorType: 'warning',
+ })
+ })
+
+ it('should return isInError false if there is no error', () => {
+ gasInputError = wrapper.instance().gasInputError({
+ labelKey: 'gasPrice',
+ insufficientBalance: false,
+ customPriceIsSafe: true,
+ value: 1,
+ })
+ assert.equal(gasInputError.isInError, false)
+ })
+ })
+
+})
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.js
new file mode 100644
index 000000000..61b681e1a
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.js
@@ -0,0 +1 @@
+export { default } from './time-remaining.component'
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.scss b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.scss
new file mode 100644
index 000000000..e2115af7f
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.scss
@@ -0,0 +1,17 @@
+.time-remaining {
+ color: #313A5E;
+ font-size: 16px;
+
+ .minutes-num, .seconds-num {
+ font-size: 16px;
+ }
+
+ .seconds-num {
+ margin-left: 7px;
+ font-size: 16px;
+ }
+
+ .minutes-label, .seconds-label {
+ font-size: 16px;
+ }
+} \ No newline at end of file
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/tests/time-remaining-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/tests/time-remaining-component.test.js
new file mode 100644
index 000000000..17f0345d5
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/tests/time-remaining-component.test.js
@@ -0,0 +1,30 @@
+import React from 'react'
+import assert from 'assert'
+import shallow from '../../../../../../../../lib/shallow-with-context'
+import TimeRemaining from '../time-remaining.component.js'
+
+describe('TimeRemaining Component', function () {
+ let wrapper
+
+ beforeEach(() => {
+ wrapper = shallow(<TimeRemaining
+ milliseconds={495000}
+ />)
+ })
+
+ describe('render()', () => {
+ it('should render the time-remaining root node', () => {
+ assert(wrapper.hasClass('time-remaining'))
+ })
+
+ it('should render minutes and seconds numbers and labels', () => {
+ const timeRemainingChildren = wrapper.children()
+ assert.equal(timeRemainingChildren.length, 4)
+ assert.equal(timeRemainingChildren.at(0).text(), 8)
+ assert.equal(timeRemainingChildren.at(1).text(), 'minutesShorthand')
+ assert.equal(timeRemainingChildren.at(2).text(), 15)
+ assert.equal(timeRemainingChildren.at(3).text(), 'secondsShorthand')
+ })
+ })
+
+})
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.component.js
new file mode 100644
index 000000000..826d41f9c
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.component.js
@@ -0,0 +1,33 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import { getTimeBreakdown } from './time-remaining.utils'
+
+export default class TimeRemaining extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ milliseconds: PropTypes.number,
+ }
+
+ render () {
+ const {
+ milliseconds,
+ } = this.props
+
+ const {
+ minutes,
+ seconds,
+ } = getTimeBreakdown(milliseconds)
+
+ return (
+ <div className="time-remaining">
+ <span className="minutes-num">{minutes}</span>
+ <span className="minutes-label">{this.context.t('minutesShorthand')}</span>
+ <span className="seconds-num">{seconds}</span>
+ <span className="seconds-label">{this.context.t('secondsShorthand')}</span>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.utils.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.utils.js
new file mode 100644
index 000000000..cf43e0acb
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.utils.js
@@ -0,0 +1,11 @@
+function getTimeBreakdown (milliseconds) {
+ return {
+ hours: Math.floor(milliseconds / 3600000),
+ minutes: Math.floor((milliseconds % 3600000) / 60000),
+ seconds: Math.floor((milliseconds % 60000) / 1000),
+ }
+}
+
+module.exports = {
+ getTimeBreakdown,
+}
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js
new file mode 100644
index 000000000..5f3925fa5
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js
@@ -0,0 +1,35 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import Loading from '../../../../ui/loading-screen'
+import GasPriceButtonGroup from '../../gas-price-button-group'
+
+export default class BasicTabContent extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ gasPriceButtonGroupProps: PropTypes.object,
+ }
+
+ render () {
+ const { t } = this.context
+ const { gasPriceButtonGroupProps } = this.props
+
+ return (
+ <div className="basic-tab-content">
+ <div className="basic-tab-content__title">{ t('estimatedProcessingTimes') }</div>
+ <div className="basic-tab-content__blurb">{ t('selectAHigherGasFee') }</div>
+ {!gasPriceButtonGroupProps.loading
+ ? <GasPriceButtonGroup
+ className="gas-price-button-group--alt"
+ showCheck={true}
+ {...gasPriceButtonGroupProps}
+ />
+ : <Loading />
+ }
+ <div className="basic-tab-content__footer-blurb">{ t('acceleratingATransaction') }</div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/index.js b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/index.js
new file mode 100644
index 000000000..078d50fce
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/index.js
@@ -0,0 +1 @@
+export { default } from './basic-tab-content.component'
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/index.scss b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/index.scss
new file mode 100644
index 000000000..e34e4e328
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/index.scss
@@ -0,0 +1,28 @@
+.basic-tab-content {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ padding-left: 21px;
+ height: 324px;
+ background: #F5F7F8;
+ border-bottom: 1px solid #d2d8dd;
+
+ &__title {
+ margin-top: 19px;
+ font-size: 16px;
+ color: $black;
+ }
+
+ &__blurb {
+ font-size: 12px;
+ color: $black;
+ margin-top: 5px;
+ margin-bottom: 15px;
+ }
+
+ &__footer-blurb {
+ font-size: 12px;
+ color: #979797;
+ margin-top: 15px;
+ }
+}
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js
new file mode 100644
index 000000000..0989ac677
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js
@@ -0,0 +1,82 @@
+import React from 'react'
+import assert from 'assert'
+import shallow from '../../../../../../../lib/shallow-with-context'
+import BasicTabContent from '../basic-tab-content.component'
+
+import GasPriceButtonGroup from '../../../gas-price-button-group'
+import Loading from '../../../../../ui/loading-screen'
+
+const mockGasPriceButtonGroupProps = {
+ buttonDataLoading: false,
+ className: 'gas-price-button-group',
+ gasButtonInfo: [
+ {
+ feeInPrimaryCurrency: '$0.52',
+ feeInSecondaryCurrency: '0.0048 ETH',
+ timeEstimate: '~ 1 min 0 sec',
+ priceInHexWei: '0xa1b2c3f',
+ },
+ {
+ feeInPrimaryCurrency: '$0.39',
+ feeInSecondaryCurrency: '0.004 ETH',
+ timeEstimate: '~ 1 min 30 sec',
+ priceInHexWei: '0xa1b2c39',
+ },
+ {
+ feeInPrimaryCurrency: '$0.30',
+ feeInSecondaryCurrency: '0.00354 ETH',
+ timeEstimate: '~ 2 min 1 sec',
+ priceInHexWei: '0xa1b2c30',
+ },
+ ],
+ handleGasPriceSelection: newPrice => console.log('NewPrice: ', newPrice),
+ noButtonActiveByDefault: true,
+ showCheck: true,
+}
+
+describe('BasicTabContent Component', function () {
+ let wrapper
+
+ beforeEach(() => {
+ wrapper = shallow(<BasicTabContent
+ gasPriceButtonGroupProps={mockGasPriceButtonGroupProps}
+ />)
+ })
+
+ describe('render', () => {
+ it('should have a title', () => {
+ assert(wrapper.find('.basic-tab-content').childAt(0).hasClass('basic-tab-content__title'))
+ })
+
+ it('should render a GasPriceButtonGroup compenent', () => {
+ assert.equal(wrapper.find(GasPriceButtonGroup).length, 1)
+ })
+
+ it('should pass correct props to GasPriceButtonGroup', () => {
+ const {
+ buttonDataLoading,
+ className,
+ gasButtonInfo,
+ handleGasPriceSelection,
+ noButtonActiveByDefault,
+ showCheck,
+ } = wrapper.find(GasPriceButtonGroup).props()
+ assert.equal(wrapper.find(GasPriceButtonGroup).length, 1)
+ assert.equal(buttonDataLoading, mockGasPriceButtonGroupProps.buttonDataLoading)
+ assert.equal(className, mockGasPriceButtonGroupProps.className)
+ assert.equal(noButtonActiveByDefault, mockGasPriceButtonGroupProps.noButtonActiveByDefault)
+ assert.equal(showCheck, mockGasPriceButtonGroupProps.showCheck)
+ assert.deepEqual(gasButtonInfo, mockGasPriceButtonGroupProps.gasButtonInfo)
+ assert.equal(JSON.stringify(handleGasPriceSelection), JSON.stringify(mockGasPriceButtonGroupProps.handleGasPriceSelection))
+ })
+
+ it('should render a loading component instead of the GasPriceButtonGroup if gasPriceButtonGroupProps.loading is true', () => {
+ wrapper.setProps({
+ gasPriceButtonGroupProps: { ...mockGasPriceButtonGroupProps, loading: true },
+ })
+
+ assert.equal(wrapper.find(GasPriceButtonGroup).length, 0)
+ assert.equal(wrapper.find(Loading).length, 1)
+ })
+ })
+})
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
new file mode 100644
index 000000000..d242f59f5
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js
@@ -0,0 +1,186 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import PageContainer from '../../../ui/page-container'
+import { Tabs, Tab } from '../../../ui/tabs'
+import AdvancedTabContent from './advanced-tab-content'
+import BasicTabContent from './basic-tab-content'
+
+export default class GasModalPageContainer extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ hideModal: PropTypes.func,
+ hideBasic: PropTypes.bool,
+ updateCustomGasPrice: PropTypes.func,
+ updateCustomGasLimit: PropTypes.func,
+ customGasPrice: PropTypes.number,
+ customGasLimit: PropTypes.number,
+ fetchBasicGasAndTimeEstimates: PropTypes.func,
+ fetchGasEstimates: PropTypes.func,
+ gasPriceButtonGroupProps: PropTypes.object,
+ infoRowProps: PropTypes.shape({
+ originalTotalFiat: PropTypes.string,
+ originalTotalEth: PropTypes.string,
+ newTotalFiat: PropTypes.string,
+ newTotalEth: PropTypes.string,
+ }),
+ onSubmit: PropTypes.func,
+ customModalGasPriceInHex: PropTypes.string,
+ customModalGasLimitInHex: PropTypes.string,
+ cancelAndClose: PropTypes.func,
+ transactionFee: PropTypes.string,
+ blockTime: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]),
+ customPriceIsSafe: PropTypes.bool,
+ isSpeedUp: PropTypes.bool,
+ disableSave: PropTypes.bool,
+ }
+
+ state = {}
+
+ componentDidMount () {
+ const promise = this.props.hideBasic
+ ? Promise.resolve(this.props.blockTime)
+ : this.props.fetchBasicGasAndTimeEstimates()
+ .then(basicEstimates => basicEstimates.blockTime)
+
+ promise
+ .then(blockTime => {
+ this.props.fetchGasEstimates(blockTime)
+ })
+ }
+
+ renderBasicTabContent (gasPriceButtonGroupProps) {
+ return (
+ <BasicTabContent
+ gasPriceButtonGroupProps={gasPriceButtonGroupProps}
+ />
+ )
+ }
+
+ renderAdvancedTabContent ({
+ convertThenUpdateCustomGasPrice,
+ convertThenUpdateCustomGasLimit,
+ customGasPrice,
+ customGasLimit,
+ newTotalFiat,
+ gasChartProps,
+ currentTimeEstimate,
+ insufficientBalance,
+ gasEstimatesLoading,
+ customPriceIsSafe,
+ isSpeedUp,
+ transactionFee,
+ }) {
+ return (
+ <AdvancedTabContent
+ updateCustomGasPrice={convertThenUpdateCustomGasPrice}
+ updateCustomGasLimit={convertThenUpdateCustomGasLimit}
+ customGasPrice={customGasPrice}
+ customGasLimit={customGasLimit}
+ timeRemaining={currentTimeEstimate}
+ transactionFee={transactionFee}
+ totalFee={newTotalFiat}
+ gasChartProps={gasChartProps}
+ insufficientBalance={insufficientBalance}
+ gasEstimatesLoading={gasEstimatesLoading}
+ customPriceIsSafe={customPriceIsSafe}
+ isSpeedUp={isSpeedUp}
+ />
+ )
+ }
+
+ renderInfoRows (newTotalFiat, newTotalEth, sendAmount, transactionFee) {
+ return (
+ <div className="gas-modal-content__info-row-wrapper">
+ <div className="gas-modal-content__info-row">
+ <div className="gas-modal-content__info-row__send-info">
+ <span className="gas-modal-content__info-row__send-info__label">{this.context.t('sendAmount')}</span>
+ <span className="gas-modal-content__info-row__send-info__value">{sendAmount}</span>
+ </div>
+ <div className="gas-modal-content__info-row__transaction-info">
+ <span className={'gas-modal-content__info-row__transaction-info__label'}>{this.context.t('transactionFee')}</span>
+ <span className="gas-modal-content__info-row__transaction-info__value">{transactionFee}</span>
+ </div>
+ <div className="gas-modal-content__info-row__total-info">
+ <span className="gas-modal-content__info-row__total-info__label">{this.context.t('newTotal')}</span>
+ <span className="gas-modal-content__info-row__total-info__value">{newTotalEth}</span>
+ </div>
+ <div className="gas-modal-content__info-row__fiat-total-info">
+ <span className="gas-modal-content__info-row__fiat-total-info__value">{newTotalFiat}</span>
+ </div>
+ </div>
+ </div>
+ )
+ }
+
+ renderTabs ({
+ originalTotalFiat,
+ originalTotalEth,
+ newTotalFiat,
+ newTotalEth,
+ sendAmount,
+ transactionFee,
+ },
+ {
+ gasPriceButtonGroupProps,
+ hideBasic,
+ ...advancedTabProps
+ }) {
+ let tabsToRender = [
+ { name: 'basic', content: this.renderBasicTabContent(gasPriceButtonGroupProps) },
+ { name: 'advanced', content: this.renderAdvancedTabContent({ transactionFee, ...advancedTabProps }) },
+ ]
+
+ if (hideBasic) {
+ tabsToRender = tabsToRender.slice(1)
+ }
+
+ return (
+ <Tabs>
+ {tabsToRender.map(({ name, content }, i) => <Tab name={this.context.t(name)} key={`gas-modal-tab-${i}`}>
+ <div className="gas-modal-content">
+ { content }
+ { this.renderInfoRows(newTotalFiat, newTotalEth, sendAmount, transactionFee) }
+ </div>
+ </Tab>
+ )}
+ </Tabs>
+ )
+ }
+
+ render () {
+ const {
+ cancelAndClose,
+ infoRowProps,
+ onSubmit,
+ customModalGasPriceInHex,
+ customModalGasLimitInHex,
+ disableSave,
+ ...tabProps
+ } = this.props
+
+ return (
+ <div className="gas-modal-page-container">
+ <PageContainer
+ title={this.context.t('customGas')}
+ subtitle={this.context.t('customGasSubTitle')}
+ tabsComponent={this.renderTabs(infoRowProps, tabProps)}
+ disabled={disableSave}
+ onCancel={() => cancelAndClose()}
+ onClose={() => cancelAndClose()}
+ onSubmit={() => {
+ onSubmit(customModalGasLimitInHex, customModalGasPriceInHex)
+ }}
+ submitText={this.context.t('save')}
+ headerCloseText={this.context.t('close')}
+ hideCancel={true}
+ />
+ </div>
+ )
+ }
+}
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
new file mode 100644
index 000000000..d541056f4
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
@@ -0,0 +1,295 @@
+import { connect } from 'react-redux'
+import { pipe, partialRight } from 'ramda'
+import GasModalPageContainer from './gas-modal-page-container.component'
+import {
+ hideModal,
+ setGasLimit,
+ setGasPrice,
+ createSpeedUpTransaction,
+ hideSidebar,
+} from '../../../../store/actions'
+import {
+ setCustomGasPrice,
+ setCustomGasLimit,
+ resetCustomData,
+ setCustomTimeEstimate,
+ fetchGasEstimates,
+ fetchBasicGasAndTimeEstimates,
+} from '../../../../ducks/gas/gas.duck'
+import {
+ hideGasButtonGroup,
+} from '../../../../ducks/send/send.duck'
+import {
+ updateGasAndCalculate,
+} from '../../../../ducks/confirm-transaction/confirm-transaction.duck'
+import {
+ conversionRateSelector as getConversionRate,
+ getCurrentCurrency,
+ getCurrentEthBalance,
+ getIsMainnet,
+ getSelectedToken,
+ isEthereumNetwork,
+ preferencesSelector,
+} from '../../../../selectors/selectors.js'
+import {
+ formatTimeEstimate,
+ getFastPriceEstimateInHexWEI,
+ getBasicGasEstimateLoadingStatus,
+ getGasEstimatesLoadingStatus,
+ getCustomGasLimit,
+ getCustomGasPrice,
+ getDefaultActiveButtonIndex,
+ getEstimatedGasPrices,
+ getEstimatedGasTimes,
+ getRenderableBasicEstimateData,
+ getBasicGasEstimateBlockTime,
+ isCustomPriceSafe,
+} from '../../../../selectors/custom-gas'
+import {
+ submittedPendingTransactionsSelector,
+} from '../../../../selectors/transactions'
+import {
+ formatCurrency,
+} from '../../../../helpers/utils/confirm-tx.util'
+import {
+ addHexWEIsToDec,
+ decEthToConvertedCurrency as ethTotalToConvertedCurrency,
+ decGWEIToHexWEI,
+ hexWEIToDecGWEI,
+} from '../../../../helpers/utils/conversions.util'
+import {
+ formatETHFee,
+} from '../../../../helpers/utils/formatters'
+import {
+ calcGasTotal,
+ isBalanceSufficient,
+} from '../../send/send.utils'
+import { addHexPrefix } from 'ethereumjs-util'
+import { getAdjacentGasPrices, extrapolateY } from '../gas-price-chart/gas-price-chart.utils'
+
+const mapStateToProps = (state, ownProps) => {
+ const { transaction = {} } = ownProps
+ const buttonDataLoading = getBasicGasEstimateLoadingStatus(state)
+ const gasEstimatesLoading = getGasEstimatesLoadingStatus(state)
+
+ 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)
+
+ const currentCurrency = getCurrentCurrency(state)
+ const conversionRate = getConversionRate(state)
+
+ const newTotalFiat = addHexWEIsToRenderableFiat(value, customGasTotal, currentCurrency, conversionRate)
+
+ const hideBasic = state.appState.modal.modalState.props.hideBasic
+
+ const customGasPrice = calcCustomGasPrice(customModalGasPriceInHex)
+
+ const gasPrices = getEstimatedGasPrices(state)
+ const estimatedTimes = getEstimatedGasTimes(state)
+ const balance = getCurrentEthBalance(state)
+
+ const { showFiatInTestnets } = preferencesSelector(state)
+ const isMainnet = getIsMainnet(state)
+ const showFiat = Boolean(isMainnet || showFiatInTestnets)
+
+ const insufficientBalance = !isBalanceSufficient({
+ amount: value,
+ gasTotal,
+ balance,
+ conversionRate,
+ })
+
+ return {
+ hideBasic,
+ isConfirm: isConfirm(state),
+ customModalGasPriceInHex,
+ customModalGasLimitInHex,
+ customGasPrice,
+ customGasLimit: calcCustomGasLimit(customModalGasLimitInHex),
+ newTotalFiat,
+ currentTimeEstimate: getRenderableTimeEstimate(customGasPrice, gasPrices, estimatedTimes),
+ blockTime: getBasicGasEstimateBlockTime(state),
+ customPriceIsSafe: isCustomPriceSafe(state),
+ gasPriceButtonGroupProps: {
+ buttonDataLoading,
+ defaultActiveButtonIndex: getDefaultActiveButtonIndex(gasButtonInfo, customModalGasPriceInHex),
+ gasButtonInfo,
+ },
+ gasChartProps: {
+ currentPrice: customGasPrice,
+ gasPrices,
+ estimatedTimes,
+ gasPricesMax: gasPrices[gasPrices.length - 1],
+ estimatedTimesMax: estimatedTimes[0],
+ },
+ infoRowProps: {
+ originalTotalFiat: addHexWEIsToRenderableFiat(value, gasTotal, currentCurrency, conversionRate),
+ originalTotalEth: addHexWEIsToRenderableEth(value, gasTotal),
+ newTotalFiat: showFiat ? newTotalFiat : '',
+ newTotalEth: addHexWEIsToRenderableEth(value, customGasTotal),
+ transactionFee: addHexWEIsToRenderableEth('0x0', customGasTotal),
+ sendAmount: addHexWEIsToRenderableEth(value, '0x0'),
+ },
+ isSpeedUp: transaction.status === 'submitted',
+ txId: transaction.id,
+ insufficientBalance,
+ gasEstimatesLoading,
+ isMainnet,
+ isEthereumNetwork: isEthereumNetwork(state),
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ const updateCustomGasPrice = newPrice => dispatch(setCustomGasPrice(addHexPrefix(newPrice)))
+
+ return {
+ cancelAndClose: () => {
+ dispatch(resetCustomData())
+ dispatch(hideModal())
+ },
+ hideModal: () => dispatch(hideModal()),
+ updateCustomGasPrice,
+ convertThenUpdateCustomGasPrice: newPrice => updateCustomGasPrice(decGWEIToHexWEI(newPrice)),
+ convertThenUpdateCustomGasLimit: newLimit => dispatch(setCustomGasLimit(addHexPrefix(newLimit.toString(16)))),
+ setGasData: (newLimit, newPrice) => {
+ dispatch(setGasLimit(newLimit))
+ dispatch(setGasPrice(newPrice))
+ },
+ updateConfirmTxGasAndCalculate: (gasLimit, gasPrice) => {
+ updateCustomGasPrice(gasPrice)
+ dispatch(setCustomGasLimit(addHexPrefix(gasLimit.toString(16))))
+ return dispatch(updateGasAndCalculate({ gasLimit, gasPrice }))
+ },
+ createSpeedUpTransaction: (txId, gasPrice) => {
+ return dispatch(createSpeedUpTransaction(txId, gasPrice))
+ },
+ hideGasButtonGroup: () => dispatch(hideGasButtonGroup()),
+ setCustomTimeEstimate: (timeEstimateInSeconds) => dispatch(setCustomTimeEstimate(timeEstimateInSeconds)),
+ hideSidebar: () => dispatch(hideSidebar()),
+ fetchGasEstimates: (blockTime) => dispatch(fetchGasEstimates(blockTime)),
+ fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()),
+ }
+}
+
+const mergeProps = (stateProps, dispatchProps, ownProps) => {
+ const { gasPriceButtonGroupProps, isConfirm, txId, isSpeedUp, insufficientBalance, customGasPrice } = stateProps
+ const {
+ updateCustomGasPrice: dispatchUpdateCustomGasPrice,
+ hideGasButtonGroup: dispatchHideGasButtonGroup,
+ setGasData: dispatchSetGasData,
+ updateConfirmTxGasAndCalculate: dispatchUpdateConfirmTxGasAndCalculate,
+ createSpeedUpTransaction: dispatchCreateSpeedUpTransaction,
+ hideSidebar: dispatchHideSidebar,
+ cancelAndClose: dispatchCancelAndClose,
+ hideModal: dispatchHideModal,
+ ...otherDispatchProps
+ } = dispatchProps
+
+ return {
+ ...stateProps,
+ ...otherDispatchProps,
+ ...ownProps,
+ onSubmit: (gasLimit, gasPrice) => {
+ if (isConfirm) {
+ dispatchUpdateConfirmTxGasAndCalculate(gasLimit, gasPrice)
+ dispatchHideModal()
+ } else if (isSpeedUp) {
+ dispatchCreateSpeedUpTransaction(txId, gasPrice)
+ dispatchHideSidebar()
+ dispatchCancelAndClose()
+ } else {
+ dispatchSetGasData(gasLimit, gasPrice)
+ dispatchHideGasButtonGroup()
+ dispatchCancelAndClose()
+ }
+ },
+ gasPriceButtonGroupProps: {
+ ...gasPriceButtonGroupProps,
+ handleGasPriceSelection: dispatchUpdateCustomGasPrice,
+ },
+ cancelAndClose: () => {
+ dispatchCancelAndClose()
+ if (isSpeedUp) {
+ dispatchHideSidebar()
+ }
+ },
+ disableSave: insufficientBalance || (isSpeedUp && customGasPrice === 0),
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(GasModalPageContainer)
+
+function isConfirm (state) {
+ return Boolean(Object.keys(state.confirmTransaction.txData).length)
+}
+
+function calcCustomGasPrice (customGasPriceInHex) {
+ return Number(hexWEIToDecGWEI(customGasPriceInHex))
+}
+
+function calcCustomGasLimit (customGasLimitInHex) {
+ return parseInt(customGasLimitInHex, 16)
+}
+
+function getTxParams (state, transactionId) {
+ const { confirmTransaction: { txData }, metamask: { send } } = state
+ const pendingTransactions = submittedPendingTransactionsSelector(state)
+ const pendingTransaction = pendingTransactions.find(({ id }) => id === transactionId)
+ const { txParams: pendingTxParams } = pendingTransaction || {}
+ return txData.txParams || pendingTxParams || {
+ from: send.from,
+ gas: send.gasLimit || '0x5208',
+ gasPrice: send.gasPrice || getFastPriceEstimateInHexWEI(state, true),
+ to: send.to,
+ value: getSelectedToken(state) ? '0x0' : send.amount,
+ }
+}
+
+function addHexWEIsToRenderableEth (aHexWEI, bHexWEI) {
+ return pipe(
+ addHexWEIsToDec,
+ formatETHFee
+ )(aHexWEI, bHexWEI)
+}
+
+function addHexWEIsToRenderableFiat (aHexWEI, bHexWEI, convertedCurrency, conversionRate) {
+ return pipe(
+ addHexWEIsToDec,
+ partialRight(ethTotalToConvertedCurrency, [convertedCurrency, conversionRate]),
+ partialRight(formatCurrency, [convertedCurrency]),
+ )(aHexWEI, bHexWEI)
+}
+
+function getRenderableTimeEstimate (currentGasPrice, gasPrices, estimatedTimes) {
+ const minGasPrice = gasPrices[0]
+ const maxGasPrice = gasPrices[gasPrices.length - 1]
+ let priceForEstimation = currentGasPrice
+ if (currentGasPrice < minGasPrice) {
+ priceForEstimation = minGasPrice
+ } else if (currentGasPrice > maxGasPrice) {
+ priceForEstimation = maxGasPrice
+ }
+
+ const {
+ closestLowerValueIndex,
+ closestHigherValueIndex,
+ closestHigherValue,
+ closestLowerValue,
+ } = getAdjacentGasPrices({ gasPrices, priceToPosition: priceForEstimation })
+
+ const newTimeEstimate = extrapolateY({
+ higherY: estimatedTimes[closestHigherValueIndex],
+ lowerY: estimatedTimes[closestLowerValueIndex],
+ higherX: closestHigherValue,
+ lowerX: closestLowerValue,
+ xForExtrapolation: priceForEstimation,
+ })
+
+ return formatTimeEstimate(newTimeEstimate, currentGasPrice > maxGasPrice, currentGasPrice < minGasPrice)
+}
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/index.js b/ui/app/components/app/gas-customization/gas-modal-page-container/index.js
new file mode 100644
index 000000000..ec0ebad22
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/index.js
@@ -0,0 +1 @@
+export { default } from './gas-modal-page-container.container'
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/index.scss b/ui/app/components/app/gas-customization/gas-modal-page-container/index.scss
new file mode 100644
index 000000000..b9e0f59c4
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/index.scss
@@ -0,0 +1,146 @@
+@import './advanced-tab-content/index';
+@import './basic-tab-content/index';
+
+.gas-modal-page-container {
+ .page-container {
+ max-width: 391px;
+ min-height: 585px;
+ overflow-y: initial;
+
+ @media screen and (max-width: $break-small) {
+ &__content {
+ display: flex;
+ overflow-y: initial;
+ }
+ }
+
+ &__header {
+ padding: 0px;
+ padding-top: 16px;
+
+ &--no-padding-bottom {
+ padding-bottom: 0;
+ }
+ }
+
+ &__footer {
+ header {
+ padding-top: 12px;
+ padding-bottom: 12px;
+ }
+ }
+
+ &__header-close-text {
+ font-size: 14px;
+ color: #4EADE7;
+ position: absolute;
+ top: 16px;
+ right: 16px;
+ cursor: pointer;
+ overflow: hidden;
+ }
+
+ &__title {
+ color: $black;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 16px;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ margin-right: 0;
+ }
+
+ &__subtitle {
+ display: none;
+ }
+
+ &__tabs {
+ margin-top: 0px;
+ }
+
+ &__tab {
+ width: 100%;
+ font-size: 14px;
+
+ &:last-of-type {
+ margin-right: 0;
+ }
+
+ &--selected {
+ color: $curious-blue;
+ border-bottom: 2px solid $curious-blue;
+ }
+ }
+ }
+}
+
+.gas-modal-content {
+ @media screen and (max-width: $break-small) {
+ width: 100%;
+ }
+
+ &__basic-tab {
+ height: 219px;
+ }
+
+
+ &__info-row, &__info-row--fade {
+ width: 100%;
+ background: $polar;
+ padding: 15px 21px;
+ display: flex;
+ flex-flow: column;
+ color: $scorpion;
+ font-size: 12px;
+
+ @media screen and (max-width: $break-small) {
+ padding: 4px 21px;
+ }
+
+ &__send-info, &__transaction-info, &__total-info, &__fiat-total-info {
+ display: flex;
+ flex-flow: row;
+ justify-content: space-between;
+ }
+
+ &__fiat-total-info {
+ justify-content: flex-end;
+ }
+
+ &__total-info {
+ &__label {
+ font-size: 16px;
+
+ @media screen and (max-width: $break-small) {
+ font-size: 14px;
+ }
+ }
+
+ &__value {
+ font-size: 16px;
+ font-weight: bold;
+
+ @media screen and (max-width: $break-small) {
+ font-size: 14px;
+ }
+ }
+ }
+
+ &__transaction-info, &__send-info {
+ &__label {
+ font-size: 12px;
+ }
+
+ &__value {
+ font-size: 14px;
+ }
+ }
+ }
+
+ &__info-row--fade {
+ background: white;
+ color: $dusty-gray;
+ border-top: 1px solid $mischka;
+ }
+}
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js
new file mode 100644
index 000000000..7557eefe5
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js
@@ -0,0 +1,274 @@
+import React from 'react'
+import assert from 'assert'
+import shallow from '../../../../../../lib/shallow-with-context'
+import sinon from 'sinon'
+import GasModalPageContainer from '../gas-modal-page-container.component.js'
+import timeout from '../../../../../../lib/test-timeout'
+
+import PageContainer from '../../../../ui/page-container'
+
+import { Tab } from '../../../../ui/tabs'
+
+const mockBasicGasEstimates = {
+ blockTime: 'mockBlockTime',
+}
+
+const propsMethodSpies = {
+ cancelAndClose: sinon.spy(),
+ onSubmit: sinon.spy(),
+ fetchBasicGasAndTimeEstimates: sinon.stub().returns(Promise.resolve(mockBasicGasEstimates)),
+ fetchGasEstimates: sinon.spy(),
+}
+
+const mockGasPriceButtonGroupProps = {
+ buttonDataLoading: false,
+ className: 'gas-price-button-group',
+ gasButtonInfo: [
+ {
+ feeInPrimaryCurrency: '$0.52',
+ feeInSecondaryCurrency: '0.0048 ETH',
+ timeEstimate: '~ 1 min 0 sec',
+ priceInHexWei: '0xa1b2c3f',
+ },
+ {
+ feeInPrimaryCurrency: '$0.39',
+ feeInSecondaryCurrency: '0.004 ETH',
+ timeEstimate: '~ 1 min 30 sec',
+ priceInHexWei: '0xa1b2c39',
+ },
+ {
+ feeInPrimaryCurrency: '$0.30',
+ feeInSecondaryCurrency: '0.00354 ETH',
+ timeEstimate: '~ 2 min 1 sec',
+ priceInHexWei: '0xa1b2c30',
+ },
+ ],
+ handleGasPriceSelection: 'mockSelectionFunction',
+ noButtonActiveByDefault: true,
+ showCheck: true,
+ newTotalFiat: 'mockNewTotalFiat',
+ newTotalEth: 'mockNewTotalEth',
+}
+const mockInfoRowProps = {
+ originalTotalFiat: 'mockOriginalTotalFiat',
+ originalTotalEth: 'mockOriginalTotalEth',
+ newTotalFiat: 'mockNewTotalFiat',
+ newTotalEth: 'mockNewTotalEth',
+ sendAmount: 'mockSendAmount',
+ transactionFee: 'mockTransactionFee',
+}
+
+const GP = GasModalPageContainer.prototype
+describe('GasModalPageContainer Component', function () {
+ let wrapper
+
+ beforeEach(() => {
+ wrapper = shallow(<GasModalPageContainer
+ cancelAndClose={propsMethodSpies.cancelAndClose}
+ onSubmit={propsMethodSpies.onSubmit}
+ fetchBasicGasAndTimeEstimates={propsMethodSpies.fetchBasicGasAndTimeEstimates}
+ fetchGasEstimates={propsMethodSpies.fetchGasEstimates}
+ updateCustomGasPrice={() => 'mockupdateCustomGasPrice'}
+ updateCustomGasLimit={() => 'mockupdateCustomGasLimit'}
+ customGasPrice={21}
+ customGasLimit={54321}
+ gasPriceButtonGroupProps={mockGasPriceButtonGroupProps}
+ infoRowProps={mockInfoRowProps}
+ currentTimeEstimate={'1 min 31 sec'}
+ customGasPriceInHex={'mockCustomGasPriceInHex'}
+ customGasLimitInHex={'mockCustomGasLimitInHex'}
+ insufficientBalance={false}
+ disableSave={false}
+ />, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } })
+ })
+
+ afterEach(() => {
+ propsMethodSpies.cancelAndClose.resetHistory()
+ })
+
+ describe('componentDidMount', () => {
+ it('should call props.fetchBasicGasAndTimeEstimates', () => {
+ propsMethodSpies.fetchBasicGasAndTimeEstimates.resetHistory()
+ assert.equal(propsMethodSpies.fetchBasicGasAndTimeEstimates.callCount, 0)
+ wrapper.instance().componentDidMount()
+ assert.equal(propsMethodSpies.fetchBasicGasAndTimeEstimates.callCount, 1)
+ })
+
+ it('should call props.fetchGasEstimates with the block time returned by fetchBasicGasAndTimeEstimates', async () => {
+ propsMethodSpies.fetchGasEstimates.resetHistory()
+ assert.equal(propsMethodSpies.fetchGasEstimates.callCount, 0)
+ wrapper.instance().componentDidMount()
+ await timeout(250)
+ assert.equal(propsMethodSpies.fetchGasEstimates.callCount, 1)
+ assert.equal(propsMethodSpies.fetchGasEstimates.getCall(0).args[0], 'mockBlockTime')
+ })
+ })
+
+ describe('render', () => {
+ it('should render a PageContainer compenent', () => {
+ assert.equal(wrapper.find(PageContainer).length, 1)
+ })
+
+ it('should pass correct props to PageContainer', () => {
+ const {
+ title,
+ subtitle,
+ disabled,
+ } = wrapper.find(PageContainer).props()
+ assert.equal(title, 'customGas')
+ assert.equal(subtitle, 'customGasSubTitle')
+ assert.equal(disabled, false)
+ })
+
+ it('should pass the correct onCancel and onClose methods to PageContainer', () => {
+ const {
+ onCancel,
+ onClose,
+ } = wrapper.find(PageContainer).props()
+ assert.equal(propsMethodSpies.cancelAndClose.callCount, 0)
+ onCancel()
+ assert.equal(propsMethodSpies.cancelAndClose.callCount, 1)
+ onClose()
+ assert.equal(propsMethodSpies.cancelAndClose.callCount, 2)
+ })
+
+ it('should pass the correct renderTabs property to PageContainer', () => {
+ sinon.stub(GP, 'renderTabs').returns('mockTabs')
+ const renderTabsWrapperTester = shallow(<GasModalPageContainer
+ fetchBasicGasAndTimeEstimates={propsMethodSpies.fetchBasicGasAndTimeEstimates}
+ fetchGasEstimates={propsMethodSpies.fetchGasEstimates}
+ />, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } })
+ const { tabsComponent } = renderTabsWrapperTester.find(PageContainer).props()
+ assert.equal(tabsComponent, 'mockTabs')
+ GasModalPageContainer.prototype.renderTabs.restore()
+ })
+ })
+
+ describe('renderTabs', () => {
+ beforeEach(() => {
+ sinon.spy(GP, 'renderBasicTabContent')
+ sinon.spy(GP, 'renderAdvancedTabContent')
+ sinon.spy(GP, 'renderInfoRows')
+ })
+
+ afterEach(() => {
+ GP.renderBasicTabContent.restore()
+ GP.renderAdvancedTabContent.restore()
+ GP.renderInfoRows.restore()
+ })
+
+ it('should render a Tabs component with "Basic" and "Advanced" tabs', () => {
+ const renderTabsResult = wrapper.instance().renderTabs(mockInfoRowProps, {
+ gasPriceButtonGroupProps: mockGasPriceButtonGroupProps,
+ otherProps: 'mockAdvancedTabProps',
+ })
+ const renderedTabs = shallow(renderTabsResult)
+ assert.equal(renderedTabs.props().className, 'tabs')
+
+ const tabs = renderedTabs.find(Tab)
+ assert.equal(tabs.length, 2)
+
+ assert.equal(tabs.at(0).props().name, 'basic')
+ assert.equal(tabs.at(1).props().name, 'advanced')
+
+ assert.equal(tabs.at(0).childAt(0).props().className, 'gas-modal-content')
+ assert.equal(tabs.at(1).childAt(0).props().className, 'gas-modal-content')
+ })
+
+ it('should call renderBasicTabContent and renderAdvancedTabContent with the expected props', () => {
+ assert.equal(GP.renderBasicTabContent.callCount, 0)
+ assert.equal(GP.renderAdvancedTabContent.callCount, 0)
+
+ wrapper.instance().renderTabs(mockInfoRowProps, { gasPriceButtonGroupProps: mockGasPriceButtonGroupProps, otherProps: 'mockAdvancedTabProps' })
+
+ assert.equal(GP.renderBasicTabContent.callCount, 1)
+ assert.equal(GP.renderAdvancedTabContent.callCount, 1)
+
+ assert.deepEqual(GP.renderBasicTabContent.getCall(0).args[0], mockGasPriceButtonGroupProps)
+ assert.deepEqual(GP.renderAdvancedTabContent.getCall(0).args[0], { transactionFee: 'mockTransactionFee', otherProps: 'mockAdvancedTabProps' })
+ })
+
+ it('should call renderInfoRows with the expected props', () => {
+ assert.equal(GP.renderInfoRows.callCount, 0)
+
+ wrapper.instance().renderTabs(mockInfoRowProps, { gasPriceButtonGroupProps: mockGasPriceButtonGroupProps, otherProps: 'mockAdvancedTabProps' })
+
+ assert.equal(GP.renderInfoRows.callCount, 2)
+
+ assert.deepEqual(GP.renderInfoRows.getCall(0).args, ['mockNewTotalFiat', 'mockNewTotalEth', 'mockSendAmount', 'mockTransactionFee'])
+ assert.deepEqual(GP.renderInfoRows.getCall(1).args, ['mockNewTotalFiat', 'mockNewTotalEth', 'mockSendAmount', 'mockTransactionFee'])
+ })
+
+ it('should not render the basic tab if hideBasic is true', () => {
+ const renderTabsResult = wrapper.instance().renderTabs(mockInfoRowProps, {
+ gasPriceButtonGroupProps: mockGasPriceButtonGroupProps,
+ otherProps: 'mockAdvancedTabProps',
+ hideBasic: true,
+ })
+
+ const renderedTabs = shallow(renderTabsResult)
+ const tabs = renderedTabs.find(Tab)
+ assert.equal(tabs.length, 1)
+ assert.equal(tabs.at(0).props().name, 'advanced')
+ })
+ })
+
+ describe('renderBasicTabContent', () => {
+ it('should render', () => {
+ const renderBasicTabContentResult = wrapper.instance().renderBasicTabContent(mockGasPriceButtonGroupProps)
+
+ assert.deepEqual(
+ renderBasicTabContentResult.props.gasPriceButtonGroupProps,
+ mockGasPriceButtonGroupProps
+ )
+ })
+ })
+
+ describe('renderAdvancedTabContent', () => {
+ it('should render with the correct props', () => {
+ const renderAdvancedTabContentResult = wrapper.instance().renderAdvancedTabContent({
+ convertThenUpdateCustomGasPrice: () => 'mockConvertThenUpdateCustomGasPrice',
+ convertThenUpdateCustomGasLimit: () => 'mockConvertThenUpdateCustomGasLimit',
+ customGasPrice: 123,
+ customGasLimit: 456,
+ newTotalFiat: '$0.30',
+ currentTimeEstimate: '1 min 31 sec',
+ gasEstimatesLoading: 'mockGasEstimatesLoading',
+ })
+ const advancedTabContentProps = renderAdvancedTabContentResult.props
+ assert.equal(advancedTabContentProps.updateCustomGasPrice(), 'mockConvertThenUpdateCustomGasPrice')
+ assert.equal(advancedTabContentProps.updateCustomGasLimit(), 'mockConvertThenUpdateCustomGasLimit')
+ assert.equal(advancedTabContentProps.customGasPrice, 123)
+ assert.equal(advancedTabContentProps.customGasLimit, 456)
+ assert.equal(advancedTabContentProps.timeRemaining, '1 min 31 sec')
+ assert.equal(advancedTabContentProps.totalFee, '$0.30')
+ assert.equal(advancedTabContentProps.gasEstimatesLoading, 'mockGasEstimatesLoading')
+ })
+ })
+
+ describe('renderInfoRows', () => {
+ it('should render the info rows with the passed data', () => {
+ const baseClassName = 'gas-modal-content__info-row'
+ const renderedInfoRowsContainer = shallow(wrapper.instance().renderInfoRows(
+ 'mockNewTotalFiat',
+ ' mockNewTotalEth',
+ ' mockSendAmount',
+ ' mockTransactionFee'
+ ))
+
+ assert(renderedInfoRowsContainer.childAt(0).hasClass(baseClassName))
+
+ const renderedInfoRows = renderedInfoRowsContainer.childAt(0).children()
+ assert.equal(renderedInfoRows.length, 4)
+ assert(renderedInfoRows.at(0).hasClass(`${baseClassName}__send-info`))
+ assert(renderedInfoRows.at(1).hasClass(`${baseClassName}__transaction-info`))
+ assert(renderedInfoRows.at(2).hasClass(`${baseClassName}__total-info`))
+ assert(renderedInfoRows.at(3).hasClass(`${baseClassName}__fiat-total-info`))
+
+ assert.equal(renderedInfoRows.at(0).text(), 'sendAmount mockSendAmount')
+ assert.equal(renderedInfoRows.at(1).text(), 'transactionFee mockTransactionFee')
+ assert.equal(renderedInfoRows.at(2).text(), 'newTotal mockNewTotalEth')
+ assert.equal(renderedInfoRows.at(3).text(), 'mockNewTotalFiat')
+ })
+ })
+})
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
new file mode 100644
index 000000000..b9eb67d2b
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js
@@ -0,0 +1,431 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+import sinon from 'sinon'
+
+let mapStateToProps
+let mapDispatchToProps
+let mergeProps
+
+const actionSpies = {
+ hideModal: sinon.spy(),
+ setGasLimit: sinon.spy(),
+ setGasPrice: sinon.spy(),
+}
+
+const gasActionSpies = {
+ setCustomGasPrice: sinon.spy(),
+ setCustomGasLimit: sinon.spy(),
+ resetCustomData: sinon.spy(),
+}
+
+const confirmTransactionActionSpies = {
+ updateGasAndCalculate: sinon.spy(),
+}
+
+const sendActionSpies = {
+ hideGasButtonGroup: sinon.spy(),
+}
+
+proxyquire('../gas-modal-page-container.container.js', {
+ 'react-redux': {
+ connect: (ms, md, mp) => {
+ mapStateToProps = ms
+ mapDispatchToProps = md
+ mergeProps = mp
+ return () => ({})
+ },
+ },
+ '../../../../selectors/custom-gas': {
+ getBasicGasEstimateLoadingStatus: (s) => `mockBasicGasEstimateLoadingStatus:${Object.keys(s).length}`,
+ getRenderableBasicEstimateData: (s) => `mockRenderableBasicEstimateData:${Object.keys(s).length}`,
+ getDefaultActiveButtonIndex: (a, b) => a + b,
+ },
+ '../../../../store/actions': actionSpies,
+ '../../../../ducks/gas/gas.duck': gasActionSpies,
+ '../../../../ducks/confirm-transaction/confirm-transaction.duck': confirmTransactionActionSpies,
+ '../../../../ducks/send/send.duck': sendActionSpies,
+ '../../../../selectors/selectors.js': {
+ getCurrentEthBalance: (state) => state.metamask.balance || '0x0',
+ },
+})
+
+describe('gas-modal-page-container container', () => {
+
+ describe('mapStateToProps()', () => {
+ it('should map the correct properties to props', () => {
+ const baseMockState = {
+ appState: {
+ modal: {
+ modalState: {
+ props: {
+ hideBasic: true,
+ },
+ },
+ },
+ },
+ metamask: {
+ send: {
+ gasLimit: '16',
+ gasPrice: '32',
+ amount: '64',
+ },
+ currentCurrency: 'abc',
+ conversionRate: 50,
+ preferences: {
+ showFiatInTestnets: false,
+ },
+ provider: {
+ type: 'mainnet',
+ },
+ },
+ gas: {
+ basicEstimates: {
+ blockTime: 12,
+ safeLow: 2,
+ },
+ customData: {
+ limit: 'aaaaaaaa',
+ price: 'ffffffff',
+ },
+ gasEstimatesLoading: false,
+ priceAndTimeEstimates: [
+ { gasprice: 3, expectedTime: 31 },
+ { gasprice: 4, expectedTime: 62 },
+ { gasprice: 5, expectedTime: 93 },
+ { gasprice: 6, expectedTime: 124 },
+ ],
+ },
+ confirmTransaction: {
+ txData: {
+ txParams: {
+ gas: '0x1600000',
+ gasPrice: '0x3200000',
+ value: '0x640000000000000',
+ },
+ },
+ },
+ }
+ const baseExpectedResult = {
+ isConfirm: true,
+ customGasPrice: 4.294967295,
+ customGasLimit: 2863311530,
+ currentTimeEstimate: '~1 min 11 sec',
+ newTotalFiat: '637.41',
+ blockTime: 12,
+ customModalGasLimitInHex: 'aaaaaaaa',
+ customModalGasPriceInHex: 'ffffffff',
+ customPriceIsSafe: true,
+ gasChartProps: {
+ 'currentPrice': 4.294967295,
+ estimatedTimes: [31, 62, 93, 124],
+ estimatedTimesMax: 31,
+ gasPrices: [3, 4, 5, 6],
+ gasPricesMax: 6,
+ },
+ gasPriceButtonGroupProps: {
+ buttonDataLoading: 'mockBasicGasEstimateLoadingStatus:4',
+ defaultActiveButtonIndex: 'mockRenderableBasicEstimateData:4ffffffff',
+ gasButtonInfo: 'mockRenderableBasicEstimateData:4',
+ },
+ gasEstimatesLoading: false,
+ hideBasic: true,
+ infoRowProps: {
+ originalTotalFiat: '637.41',
+ originalTotalEth: '12.748189 ETH',
+ newTotalFiat: '637.41',
+ newTotalEth: '12.748189 ETH',
+ sendAmount: '0.45036 ETH',
+ transactionFee: '12.297829 ETH',
+ },
+ insufficientBalance: true,
+ isSpeedUp: false,
+ txId: 34,
+ isEthereumNetwork: false,
+ isMainnet: true,
+ }
+ const baseMockOwnProps = { transaction: { id: 34 } }
+ const tests = [
+ { mockState: baseMockState, expectedResult: baseExpectedResult, mockOwnProps: baseMockOwnProps },
+ {
+ mockState: Object.assign({}, baseMockState, {
+ metamask: { ...baseMockState.metamask, balance: '0xfffffffffffffffffffff' },
+ }),
+ expectedResult: Object.assign({}, baseExpectedResult, { insufficientBalance: false }),
+ mockOwnProps: baseMockOwnProps,
+ },
+ {
+ mockState: baseMockState,
+ mockOwnProps: Object.assign({}, baseMockOwnProps, {
+ transaction: { id: 34, status: 'submitted' },
+ }),
+ expectedResult: Object.assign({}, baseExpectedResult, { isSpeedUp: true }),
+ },
+ {
+ mockState: Object.assign({}, baseMockState, {
+ metamask: {
+ ...baseMockState.metamask,
+ preferences: {
+ ...baseMockState.metamask.preferences,
+ showFiatInTestnets: false,
+ },
+ provider: {
+ ...baseMockState.metamask.provider,
+ type: 'rinkeby',
+ },
+ },
+ }),
+ mockOwnProps: baseMockOwnProps,
+ expectedResult: {
+ ...baseExpectedResult,
+ infoRowProps: {
+ ...baseExpectedResult.infoRowProps,
+ newTotalFiat: '',
+ },
+ isMainnet: false,
+ },
+ },
+ {
+ mockState: Object.assign({}, baseMockState, {
+ metamask: {
+ ...baseMockState.metamask,
+ preferences: {
+ ...baseMockState.metamask.preferences,
+ showFiatInTestnets: true,
+ },
+ provider: {
+ ...baseMockState.metamask.provider,
+ type: 'rinkeby',
+ },
+ },
+ }),
+ mockOwnProps: baseMockOwnProps,
+ expectedResult: {
+ ...baseExpectedResult,
+ isMainnet: false,
+ },
+ },
+ {
+ mockState: Object.assign({}, baseMockState, {
+ metamask: {
+ ...baseMockState.metamask,
+ preferences: {
+ ...baseMockState.metamask.preferences,
+ showFiatInTestnets: true,
+ },
+ provider: {
+ ...baseMockState.metamask.provider,
+ type: 'mainnet',
+ },
+ },
+ }),
+ mockOwnProps: baseMockOwnProps,
+ expectedResult: baseExpectedResult,
+ },
+ ]
+
+ let result
+ tests.forEach(({ mockState, mockOwnProps, expectedResult}) => {
+ result = mapStateToProps(mockState, mockOwnProps)
+ assert.deepEqual(result, expectedResult)
+ })
+ })
+
+ })
+
+ describe('mapDispatchToProps()', () => {
+ let dispatchSpy
+ let mapDispatchToPropsObject
+
+ beforeEach(() => {
+ dispatchSpy = sinon.spy()
+ mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
+ })
+
+ afterEach(() => {
+ actionSpies.hideModal.resetHistory()
+ gasActionSpies.setCustomGasPrice.resetHistory()
+ gasActionSpies.setCustomGasLimit.resetHistory()
+ })
+
+ describe('hideGasButtonGroup()', () => {
+ it('should dispatch a hideGasButtonGroup action', () => {
+ mapDispatchToPropsObject.hideGasButtonGroup()
+ assert(dispatchSpy.calledOnce)
+ assert(sendActionSpies.hideGasButtonGroup.calledOnce)
+ })
+ })
+
+ describe('cancelAndClose()', () => {
+ it('should dispatch a hideModal action', () => {
+ mapDispatchToPropsObject.cancelAndClose()
+ assert(dispatchSpy.calledTwice)
+ assert(actionSpies.hideModal.calledOnce)
+ assert(gasActionSpies.resetCustomData.calledOnce)
+ })
+ })
+
+ describe('updateCustomGasPrice()', () => {
+ it('should dispatch a setCustomGasPrice action with the arg passed to updateCustomGasPrice hex prefixed', () => {
+ mapDispatchToPropsObject.updateCustomGasPrice('ffff')
+ assert(dispatchSpy.calledOnce)
+ assert(gasActionSpies.setCustomGasPrice.calledOnce)
+ assert.equal(gasActionSpies.setCustomGasPrice.getCall(0).args[0], '0xffff')
+ })
+ })
+
+ describe('convertThenUpdateCustomGasPrice()', () => {
+ it('should dispatch a setCustomGasPrice action with the arg passed to convertThenUpdateCustomGasPrice converted to WEI', () => {
+ mapDispatchToPropsObject.convertThenUpdateCustomGasPrice('0xffff')
+ assert(dispatchSpy.calledOnce)
+ assert(gasActionSpies.setCustomGasPrice.calledOnce)
+ assert.equal(gasActionSpies.setCustomGasPrice.getCall(0).args[0], '0x3b9a8e653600')
+ })
+ })
+
+
+ describe('convertThenUpdateCustomGasLimit()', () => {
+ it('should dispatch a setCustomGasLimit action with the arg passed to convertThenUpdateCustomGasLimit converted to hex', () => {
+ mapDispatchToPropsObject.convertThenUpdateCustomGasLimit(16)
+ assert(dispatchSpy.calledOnce)
+ assert(gasActionSpies.setCustomGasLimit.calledOnce)
+ assert.equal(gasActionSpies.setCustomGasLimit.getCall(0).args[0], '0x10')
+ })
+ })
+
+ describe('setGasData()', () => {
+ it('should dispatch a setGasPrice and setGasLimit action with the correct props', () => {
+ mapDispatchToPropsObject.setGasData('ffff', 'aaaa')
+ assert(dispatchSpy.calledTwice)
+ assert(actionSpies.setGasPrice.calledOnce)
+ assert(actionSpies.setGasLimit.calledOnce)
+ assert.equal(actionSpies.setGasLimit.getCall(0).args[0], 'ffff')
+ assert.equal(actionSpies.setGasPrice.getCall(0).args[0], 'aaaa')
+ })
+ })
+
+ describe('updateConfirmTxGasAndCalculate()', () => {
+ it('should dispatch a updateGasAndCalculate action with the correct props', () => {
+ mapDispatchToPropsObject.updateConfirmTxGasAndCalculate('ffff', 'aaaa')
+ assert.equal(dispatchSpy.callCount, 3)
+ assert(confirmTransactionActionSpies.updateGasAndCalculate.calledOnce)
+ assert.deepEqual(confirmTransactionActionSpies.updateGasAndCalculate.getCall(0).args[0], { gasLimit: 'ffff', gasPrice: 'aaaa' })
+ })
+ })
+
+ })
+
+ describe('mergeProps', () => {
+ let stateProps
+ let dispatchProps
+ let ownProps
+
+ beforeEach(() => {
+ stateProps = {
+ gasPriceButtonGroupProps: {
+ someGasPriceButtonGroupProp: 'foo',
+ anotherGasPriceButtonGroupProp: 'bar',
+ },
+ isConfirm: true,
+ someOtherStateProp: 'baz',
+ }
+ dispatchProps = {
+ updateCustomGasPrice: sinon.spy(),
+ hideGasButtonGroup: sinon.spy(),
+ setGasData: sinon.spy(),
+ updateConfirmTxGasAndCalculate: sinon.spy(),
+ someOtherDispatchProp: sinon.spy(),
+ createSpeedUpTransaction: sinon.spy(),
+ hideSidebar: sinon.spy(),
+ hideModal: sinon.spy(),
+ cancelAndClose: sinon.spy(),
+ }
+ ownProps = { someOwnProp: 123 }
+ })
+
+ afterEach(() => {
+ dispatchProps.updateCustomGasPrice.resetHistory()
+ dispatchProps.hideGasButtonGroup.resetHistory()
+ dispatchProps.setGasData.resetHistory()
+ dispatchProps.updateConfirmTxGasAndCalculate.resetHistory()
+ dispatchProps.someOtherDispatchProp.resetHistory()
+ dispatchProps.createSpeedUpTransaction.resetHistory()
+ dispatchProps.hideSidebar.resetHistory()
+ dispatchProps.hideModal.resetHistory()
+ })
+ it('should return the expected props when isConfirm is true', () => {
+ const result = mergeProps(stateProps, dispatchProps, ownProps)
+
+ assert.equal(result.isConfirm, true)
+ assert.equal(result.someOtherStateProp, 'baz')
+ assert.equal(result.gasPriceButtonGroupProps.someGasPriceButtonGroupProp, 'foo')
+ assert.equal(result.gasPriceButtonGroupProps.anotherGasPriceButtonGroupProp, 'bar')
+ assert.equal(result.someOwnProp, 123)
+
+ assert.equal(dispatchProps.updateConfirmTxGasAndCalculate.callCount, 0)
+ assert.equal(dispatchProps.setGasData.callCount, 0)
+ assert.equal(dispatchProps.hideGasButtonGroup.callCount, 0)
+ assert.equal(dispatchProps.hideModal.callCount, 0)
+
+ result.onSubmit()
+
+ assert.equal(dispatchProps.updateConfirmTxGasAndCalculate.callCount, 1)
+ assert.equal(dispatchProps.setGasData.callCount, 0)
+ assert.equal(dispatchProps.hideGasButtonGroup.callCount, 0)
+ assert.equal(dispatchProps.hideModal.callCount, 1)
+
+ assert.equal(dispatchProps.updateCustomGasPrice.callCount, 0)
+ result.gasPriceButtonGroupProps.handleGasPriceSelection()
+ assert.equal(dispatchProps.updateCustomGasPrice.callCount, 1)
+
+ assert.equal(dispatchProps.someOtherDispatchProp.callCount, 0)
+ result.someOtherDispatchProp()
+ assert.equal(dispatchProps.someOtherDispatchProp.callCount, 1)
+ })
+
+ it('should return the expected props when isConfirm is false', () => {
+ const result = mergeProps(Object.assign({}, stateProps, { isConfirm: false }), dispatchProps, ownProps)
+
+ assert.equal(result.isConfirm, false)
+ assert.equal(result.someOtherStateProp, 'baz')
+ assert.equal(result.gasPriceButtonGroupProps.someGasPriceButtonGroupProp, 'foo')
+ assert.equal(result.gasPriceButtonGroupProps.anotherGasPriceButtonGroupProp, 'bar')
+ assert.equal(result.someOwnProp, 123)
+
+ assert.equal(dispatchProps.updateConfirmTxGasAndCalculate.callCount, 0)
+ assert.equal(dispatchProps.setGasData.callCount, 0)
+ assert.equal(dispatchProps.hideGasButtonGroup.callCount, 0)
+ assert.equal(dispatchProps.cancelAndClose.callCount, 0)
+
+ result.onSubmit('mockNewLimit', 'mockNewPrice')
+
+ assert.equal(dispatchProps.updateConfirmTxGasAndCalculate.callCount, 0)
+ assert.equal(dispatchProps.setGasData.callCount, 1)
+ assert.deepEqual(dispatchProps.setGasData.getCall(0).args, ['mockNewLimit', 'mockNewPrice'])
+ assert.equal(dispatchProps.hideGasButtonGroup.callCount, 1)
+ assert.equal(dispatchProps.cancelAndClose.callCount, 1)
+
+ assert.equal(dispatchProps.updateCustomGasPrice.callCount, 0)
+ result.gasPriceButtonGroupProps.handleGasPriceSelection()
+ assert.equal(dispatchProps.updateCustomGasPrice.callCount, 1)
+
+ assert.equal(dispatchProps.someOtherDispatchProp.callCount, 0)
+ result.someOtherDispatchProp()
+ assert.equal(dispatchProps.someOtherDispatchProp.callCount, 1)
+ })
+
+ it('should dispatch the expected actions from obSubmit when isConfirm is false and isSpeedUp is true', () => {
+ const result = mergeProps(Object.assign({}, stateProps, { isSpeedUp: true, isConfirm: false }), dispatchProps, ownProps)
+
+ result.onSubmit()
+
+ assert.equal(dispatchProps.updateConfirmTxGasAndCalculate.callCount, 0)
+ assert.equal(dispatchProps.setGasData.callCount, 0)
+ assert.equal(dispatchProps.hideGasButtonGroup.callCount, 0)
+ assert.equal(dispatchProps.cancelAndClose.callCount, 1)
+
+ assert.equal(dispatchProps.createSpeedUpTransaction.callCount, 1)
+ assert.equal(dispatchProps.hideSidebar.callCount, 1)
+ })
+ })
+
+})
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
new file mode 100644
index 000000000..0456f5262
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js
@@ -0,0 +1,89 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import ButtonGroup from '../../../ui/button-group'
+import Button from '../../../ui/button'
+
+const GAS_OBJECT_PROPTYPES_SHAPE = {
+ label: PropTypes.string,
+ feeInPrimaryCurrency: PropTypes.string,
+ feeInSecondaryCurrency: PropTypes.string,
+ timeEstimate: PropTypes.string,
+ priceInHexWei: PropTypes.string,
+}
+
+export default class GasPriceButtonGroup extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ buttonDataLoading: PropTypes.bool,
+ className: PropTypes.string,
+ defaultActiveButtonIndex: PropTypes.number,
+ gasButtonInfo: PropTypes.arrayOf(PropTypes.shape(GAS_OBJECT_PROPTYPES_SHAPE)),
+ handleGasPriceSelection: PropTypes.func,
+ newActiveButtonIndex: PropTypes.number,
+ noButtonActiveByDefault: PropTypes.bool,
+ showCheck: PropTypes.bool,
+ }
+
+ renderButtonContent ({
+ labelKey,
+ feeInPrimaryCurrency,
+ feeInSecondaryCurrency,
+ timeEstimate,
+ }, {
+ className,
+ showCheck,
+ }) {
+ return (<div>
+ { labelKey && <div className={`${className}__label`}>{ this.context.t(labelKey) }</div> }
+ { timeEstimate && <div className={`${className}__time-estimate`}>{ timeEstimate }</div> }
+ { feeInPrimaryCurrency && <div className={`${className}__primary-currency`}>{ feeInPrimaryCurrency }</div> }
+ { feeInSecondaryCurrency && <div className={`${className}__secondary-currency`}>{ feeInSecondaryCurrency }</div> }
+ { showCheck && <div className="button-check-wrapper"><i className="fa fa-check fa-sm" /></div> }
+ </div>)
+ }
+
+ renderButton ({
+ priceInHexWei,
+ ...renderableGasInfo
+ }, {
+ buttonDataLoading,
+ handleGasPriceSelection,
+ ...buttonContentPropsAndFlags
+ }, index) {
+ return (
+ <Button
+ onClick={() => handleGasPriceSelection(priceInHexWei)}
+ key={`gas-price-button-${index}`}
+ >
+ {this.renderButtonContent(renderableGasInfo, buttonContentPropsAndFlags)}
+ </Button>
+ )
+ }
+
+ render () {
+ const {
+ gasButtonInfo,
+ defaultActiveButtonIndex = 1,
+ newActiveButtonIndex,
+ noButtonActiveByDefault = false,
+ buttonDataLoading,
+ ...buttonPropsAndFlags
+ } = this.props
+
+ return (
+ !buttonDataLoading
+ ? <ButtonGroup
+ className={buttonPropsAndFlags.className}
+ defaultActiveButtonIndex={defaultActiveButtonIndex}
+ newActiveButtonIndex={newActiveButtonIndex}
+ noButtonActiveByDefault={noButtonActiveByDefault}
+ >
+ { gasButtonInfo.map((obj, index) => this.renderButton(obj, buttonPropsAndFlags, index)) }
+ </ButtonGroup>
+ : <div className={`${buttonPropsAndFlags.className}__loading-container`}>{ this.context.t('loading') }</div>
+ )
+ }
+}
diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/index.js b/ui/app/components/app/gas-customization/gas-price-button-group/index.js
new file mode 100644
index 000000000..775648330
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-price-button-group/index.js
@@ -0,0 +1 @@
+export { default } from './gas-price-button-group.component'
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
new file mode 100644
index 000000000..cb2f3ecf1
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-price-button-group/index.scss
@@ -0,0 +1,238 @@
+.gas-price-button-group {
+ margin-top: 22px;
+ display: flex;
+ justify-content: space-evenly;
+ width: 100%;
+ padding-left: 20px;
+ padding-right: 20px;
+
+ &__primary-currency {
+ font-size: 18px;
+ height: 20.5px;
+ margin-bottom: 7.5px;
+ }
+
+ &__time-estimate {
+ margin-top: 5.5px;
+ color: $silver-chalice;
+ height: 15.4px;
+ }
+
+ &__loading-container {
+ height: 130px;
+ }
+
+ .button-group__button, .button-group__button--active {
+ height: 130px;
+ max-width: 108px;
+ font-size: 12px;
+ flex-direction: column;
+ align-items: center;
+ display: flex;
+ padding-top: 17px;
+ border-radius: 4px;
+ border: 2px solid $spindle;
+ background: $white;
+ color: $scorpion;
+
+ div {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ i {
+ &:last-child {
+ display: none;
+ }
+ }
+ }
+
+ .button-group__button--active {
+ border: 2px solid $curious-blue;
+ color: $scorpion;
+
+ i {
+ &:last-child {
+ display: flex;
+ color: $curious-blue;
+ margin-top: 8px
+ }
+ }
+ }
+}
+
+.gas-price-button-group--small {
+ display: flex;
+ justify-content: stretch;
+
+ @media screen and (max-width: $break-small) {
+ max-width: 260px;
+ }
+
+ &__button-fiat-price {
+ font-size: 13px;
+ }
+
+ &__button-label {
+ font-size: 16px;
+ }
+
+ &__label {
+ font-weight: 500;
+ }
+
+ &__primary-currency {
+ font-size: 12px;
+
+ @media screen and (max-width: 575px) {
+ font-size: 10px;
+ }
+ }
+
+ &__secondary-currency {
+ font-size: 12px;
+
+ @media screen and (max-width: 575px) {
+ font-size: 10px;
+ }
+ }
+
+ &__loading-container {
+ height: 78px;
+ }
+
+ .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;
+ }
+
+ div {
+ display: flex;
+ flex-flow: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+ }
+
+ i {
+ &:last-child {
+ display: none;
+ }
+ }
+ }
+
+ .button-group__button--active {
+ color: $white;
+ background: $dodger-blue;
+
+ i {
+ &:last-child {
+ display: flex;
+ color: $curious-blue;
+ margin-top: 10px
+ }
+ }
+ }
+}
+
+.gas-price-button-group--alt {
+ display: flex;
+ justify-content: stretch;
+ width: 95%;
+
+ &__button-fiat-price {
+ font-size: 13px;
+ }
+
+ &__button-label {
+ font-size: 16px;
+ }
+
+ &__label {
+ font-weight: 500;
+ font-size: 10px;
+ text-transform: capitalize;
+ }
+
+ &__primary-currency {
+ font-size: 11px;
+ margin-top: 3px;
+ }
+
+ &__secondary-currency {
+ font-size: 11px;
+ }
+
+ &__loading-container {
+ height: 78px;
+ }
+
+ &__time-estimate {
+ font-size: 14px;
+ font-weight: 500;
+ margin-top: 4px;
+ color: $black;
+ }
+
+ .button-group__button, .button-group__button--active {
+ height: 78px;
+ background: white;
+ color: #2A4055;
+ width: 108px;
+ height: 97px;
+ box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.151579);
+ border-radius: 6px;
+ border: none;
+
+ div {
+ display: flex;
+ flex-flow: column;;
+ align-items: flex-start;
+ justify-content: flex-start;
+ position: relative;
+ }
+
+ .button-check-wrapper {
+ display: none;
+ }
+
+ &:first-child {
+ margin-right: 6px;
+ }
+
+ &:last-child {
+ margin-left: 6px;
+ }
+ }
+
+ .button-group__button--active {
+ background: #F7FCFF;
+ border: 2px solid #2C8BDC;
+
+ .button-check-wrapper {
+ height: 16px;
+ width: 16px;
+ border-radius: 8px;
+ position: absolute;
+ top: -11px;
+ right: -10px;
+ background: #D5ECFA;
+ display: flex;
+ flex-flow: row;
+ justify-content: center;
+ align-items: center;
+ }
+
+ i {
+ display: flex;
+ color: $curious-blue;
+ font-size: 12px;
+ }
+ }
+}
diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js b/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js
new file mode 100644
index 000000000..37840a8a5
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js
@@ -0,0 +1,233 @@
+import React from 'react'
+import assert from 'assert'
+import shallow from '../../../../../../lib/shallow-with-context'
+import sinon from 'sinon'
+import GasPriceButtonGroup from '../gas-price-button-group.component'
+
+import ButtonGroup from '../../../../ui/button-group'
+
+const mockGasPriceButtonGroupProps = {
+ buttonDataLoading: false,
+ className: 'gas-price-button-group',
+ gasButtonInfo: [
+ {
+ feeInPrimaryCurrency: '$0.52',
+ feeInSecondaryCurrency: '0.0048 ETH',
+ timeEstimate: '~ 1 min 0 sec',
+ priceInHexWei: '0xa1b2c3f',
+ },
+ {
+ feeInPrimaryCurrency: '$0.39',
+ feeInSecondaryCurrency: '0.004 ETH',
+ timeEstimate: '~ 1 min 30 sec',
+ priceInHexWei: '0xa1b2c39',
+ },
+ {
+ feeInPrimaryCurrency: '$0.30',
+ feeInSecondaryCurrency: '0.00354 ETH',
+ timeEstimate: '~ 2 min 1 sec',
+ priceInHexWei: '0xa1b2c30',
+ },
+ ],
+ handleGasPriceSelection: sinon.spy(),
+ noButtonActiveByDefault: true,
+ defaultActiveButtonIndex: 2,
+ showCheck: true,
+}
+
+const mockButtonPropsAndFlags = Object.assign({}, {
+ className: mockGasPriceButtonGroupProps.className,
+ handleGasPriceSelection: mockGasPriceButtonGroupProps.handleGasPriceSelection,
+ showCheck: mockGasPriceButtonGroupProps.showCheck,
+})
+
+sinon.spy(GasPriceButtonGroup.prototype, 'renderButton')
+sinon.spy(GasPriceButtonGroup.prototype, 'renderButtonContent')
+
+describe('GasPriceButtonGroup Component', function () {
+ let wrapper
+
+ beforeEach(() => {
+ wrapper = shallow(<GasPriceButtonGroup
+ {...mockGasPriceButtonGroupProps}
+ />)
+ })
+
+ afterEach(() => {
+ GasPriceButtonGroup.prototype.renderButton.resetHistory()
+ GasPriceButtonGroup.prototype.renderButtonContent.resetHistory()
+ mockGasPriceButtonGroupProps.handleGasPriceSelection.resetHistory()
+ })
+
+ describe('render', () => {
+ it('should render a ButtonGroup', () => {
+ assert(wrapper.is(ButtonGroup))
+ })
+
+ it('should render the correct props on the ButtonGroup', () => {
+ const {
+ className,
+ defaultActiveButtonIndex,
+ noButtonActiveByDefault,
+ } = wrapper.props()
+ assert.equal(className, 'gas-price-button-group')
+ assert.equal(defaultActiveButtonIndex, 2)
+ assert.equal(noButtonActiveByDefault, true)
+ })
+
+ function renderButtonArgsTest (i, mockButtonPropsAndFlags) {
+ assert.deepEqual(
+ GasPriceButtonGroup.prototype.renderButton.getCall(i).args,
+ [
+ Object.assign({}, mockGasPriceButtonGroupProps.gasButtonInfo[i]),
+ mockButtonPropsAndFlags,
+ i,
+ ]
+ )
+ }
+
+ it('should call this.renderButton 3 times, with the correct args', () => {
+ assert.equal(GasPriceButtonGroup.prototype.renderButton.callCount, 3)
+ renderButtonArgsTest(0, mockButtonPropsAndFlags)
+ renderButtonArgsTest(1, mockButtonPropsAndFlags)
+ renderButtonArgsTest(2, mockButtonPropsAndFlags)
+ })
+
+ it('should show loading if buttonDataLoading', () => {
+ wrapper.setProps({ buttonDataLoading: true })
+ assert(wrapper.is('div'))
+ assert(wrapper.hasClass('gas-price-button-group__loading-container'))
+ assert.equal(wrapper.text(), 'loading')
+ })
+ })
+
+ describe('renderButton', () => {
+ let wrappedRenderButtonResult
+
+ beforeEach(() => {
+ GasPriceButtonGroup.prototype.renderButtonContent.resetHistory()
+ const renderButtonResult = GasPriceButtonGroup.prototype.renderButton(
+ Object.assign({}, mockGasPriceButtonGroupProps.gasButtonInfo[0]),
+ mockButtonPropsAndFlags
+ )
+ wrappedRenderButtonResult = shallow(renderButtonResult)
+ })
+
+ it('should render a button', () => {
+ assert.equal(wrappedRenderButtonResult.type(), 'button')
+ })
+
+ it('should call the correct method when clicked', () => {
+ assert.equal(mockGasPriceButtonGroupProps.handleGasPriceSelection.callCount, 0)
+ wrappedRenderButtonResult.props().onClick()
+ assert.equal(mockGasPriceButtonGroupProps.handleGasPriceSelection.callCount, 1)
+ assert.deepEqual(
+ mockGasPriceButtonGroupProps.handleGasPriceSelection.getCall(0).args,
+ [mockGasPriceButtonGroupProps.gasButtonInfo[0].priceInHexWei]
+ )
+ })
+
+ it('should call this.renderButtonContent with the correct args', () => {
+ assert.equal(GasPriceButtonGroup.prototype.renderButtonContent.callCount, 1)
+ const {
+ feeInPrimaryCurrency,
+ feeInSecondaryCurrency,
+ timeEstimate,
+ } = mockGasPriceButtonGroupProps.gasButtonInfo[0]
+ const {
+ showCheck,
+ className,
+ } = mockGasPriceButtonGroupProps
+ assert.deepEqual(
+ GasPriceButtonGroup.prototype.renderButtonContent.getCall(0).args,
+ [
+ {
+ feeInPrimaryCurrency,
+ feeInSecondaryCurrency,
+ timeEstimate,
+ },
+ {
+ showCheck,
+ className,
+ },
+ ]
+ )
+ })
+ })
+
+ describe('renderButtonContent', () => {
+ it('should render a label if passed a labelKey', () => {
+ const renderButtonContentResult = wrapper.instance().renderButtonContent({
+ labelKey: 'mockLabelKey',
+ }, {
+ className: 'someClass',
+ })
+ const wrappedRenderButtonContentResult = shallow(renderButtonContentResult)
+ assert.equal(wrappedRenderButtonContentResult.childAt(0).children().length, 1)
+ assert.equal(wrappedRenderButtonContentResult.find('.someClass__label').text(), 'mockLabelKey')
+ })
+
+ it('should render a feeInPrimaryCurrency if passed a feeInPrimaryCurrency', () => {
+ const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent({
+ feeInPrimaryCurrency: 'mockFeeInPrimaryCurrency',
+ }, {
+ className: 'someClass',
+ })
+ const wrappedRenderButtonContentResult = shallow(renderButtonContentResult)
+ assert.equal(wrappedRenderButtonContentResult.childAt(0).children().length, 1)
+ assert.equal(wrappedRenderButtonContentResult.find('.someClass__primary-currency').text(), 'mockFeeInPrimaryCurrency')
+ })
+
+ it('should render a feeInSecondaryCurrency if passed a feeInSecondaryCurrency', () => {
+ const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent({
+ feeInSecondaryCurrency: 'mockFeeInSecondaryCurrency',
+ }, {
+ className: 'someClass',
+ })
+ const wrappedRenderButtonContentResult = shallow(renderButtonContentResult)
+ assert.equal(wrappedRenderButtonContentResult.childAt(0).children().length, 1)
+ assert.equal(wrappedRenderButtonContentResult.find('.someClass__secondary-currency').text(), 'mockFeeInSecondaryCurrency')
+ })
+
+ it('should render a timeEstimate if passed a timeEstimate', () => {
+ const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent({
+ timeEstimate: 'mockTimeEstimate',
+ }, {
+ className: 'someClass',
+ })
+ const wrappedRenderButtonContentResult = shallow(renderButtonContentResult)
+ assert.equal(wrappedRenderButtonContentResult.childAt(0).children().length, 1)
+ assert.equal(wrappedRenderButtonContentResult.find('.someClass__time-estimate').text(), 'mockTimeEstimate')
+ })
+
+ it('should render a check if showCheck is true', () => {
+ const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent({}, {
+ className: 'someClass',
+ showCheck: true,
+ })
+ const wrappedRenderButtonContentResult = shallow(renderButtonContentResult)
+ assert.equal(wrappedRenderButtonContentResult.find('.fa-check').length, 1)
+ })
+
+ it('should render all elements if all args passed', () => {
+ const renderButtonContentResult = wrapper.instance().renderButtonContent({
+ labelKey: 'mockLabel',
+ feeInPrimaryCurrency: 'mockFeeInPrimaryCurrency',
+ feeInSecondaryCurrency: 'mockFeeInSecondaryCurrency',
+ timeEstimate: 'mockTimeEstimate',
+ }, {
+ className: 'someClass',
+ showCheck: true,
+ })
+ const wrappedRenderButtonContentResult = shallow(renderButtonContentResult)
+ assert.equal(wrappedRenderButtonContentResult.children().length, 5)
+ })
+
+
+ it('should render no elements if all args passed', () => {
+ const renderButtonContentResult = GasPriceButtonGroup.prototype.renderButtonContent({}, {})
+ const wrappedRenderButtonContentResult = shallow(renderButtonContentResult)
+ assert.equal(wrappedRenderButtonContentResult.children().length, 0)
+ })
+ })
+})
diff --git a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.component.js b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.component.js
new file mode 100644
index 000000000..c0eaf4852
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.component.js
@@ -0,0 +1,108 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import * as d3 from 'd3'
+import {
+ generateChart,
+ getCoordinateData,
+ handleChartUpdate,
+ hideDataUI,
+ setTickPosition,
+ handleMouseMove,
+} from './gas-price-chart.utils.js'
+
+export default class GasPriceChart extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ gasPrices: PropTypes.array,
+ estimatedTimes: PropTypes.array,
+ gasPricesMax: PropTypes.number,
+ estimatedTimesMax: PropTypes.number,
+ currentPrice: PropTypes.number,
+ updateCustomGasPrice: PropTypes.func,
+ }
+
+ renderChart ({
+ currentPrice,
+ gasPrices,
+ estimatedTimes,
+ gasPricesMax,
+ estimatedTimesMax,
+ updateCustomGasPrice,
+ }) {
+ const chart = generateChart(gasPrices, estimatedTimes, gasPricesMax, estimatedTimesMax, this.context.t)
+ setTimeout(function () {
+ setTickPosition('y', 0, -5, 8)
+ setTickPosition('y', 1, -3, -5)
+ setTickPosition('x', 0, 3)
+ setTickPosition('x', 1, 3, -8)
+
+ const { x: domainX } = getCoordinateData('.domain')
+ const { x: yAxisX } = getCoordinateData('.c3-axis-y-label')
+ const { x: tickX } = getCoordinateData('.c3-axis-x .tick')
+
+ d3.select('.c3-axis-x .tick').attr('transform', 'translate(' + (domainX - tickX) / 2 + ', 0)')
+ d3.select('.c3-axis-x-label').attr('transform', 'translate(0,-15)')
+ d3.select('.c3-axis-y-label').attr('transform', 'translate(' + (domainX - yAxisX - 12) + ', 2) rotate(-90)')
+ d3.select('.c3-xgrid-focus line').attr('y2', 98)
+
+ d3.select('.c3-chart').on('mouseout', () => {
+ hideDataUI(chart, '#overlayed-circle')
+ })
+
+ d3.select('.c3-chart').on('click', () => {
+ const { x: newGasPrice } = d3.select('#overlayed-circle').datum()
+ updateCustomGasPrice(newGasPrice)
+ })
+
+ const { x: chartXStart, width: chartWidth } = getCoordinateData('.c3-areas-data1')
+
+ handleChartUpdate({
+ chart,
+ gasPrices,
+ newPrice: currentPrice,
+ cssId: '#set-circle',
+ })
+
+ d3.select('.c3-chart').on('mousemove', function () {
+ handleMouseMove({
+ xMousePos: d3.event.clientX,
+ chartXStart,
+ chartWidth,
+ gasPrices,
+ estimatedTimes,
+ chart,
+ })
+ })
+ }, 0)
+
+ this.chart = chart
+ }
+
+ componentDidUpdate (prevProps) {
+ const { gasPrices, currentPrice: newPrice } = this.props
+
+ if (prevProps.currentPrice !== newPrice) {
+ handleChartUpdate({
+ chart: this.chart,
+ gasPrices,
+ newPrice,
+ cssId: '#set-circle',
+ })
+ }
+ }
+
+ componentDidMount () {
+ this.renderChart(this.props)
+ }
+
+ render () {
+ return (
+ <div className="gas-price-chart" id="container">
+ <div className="gas-price-chart__root" id="chart"></div>
+ </div>
+ )
+ }
+}
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
new file mode 100644
index 000000000..f19dafcc1
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js
@@ -0,0 +1,354 @@
+import * as d3 from 'd3'
+import c3 from 'c3'
+import BigNumber from 'bignumber.js'
+
+const newBigSigDig = n => (new BigNumber(n.toPrecision(15)))
+const createOp = (a, b, op) => (newBigSigDig(a))[op](newBigSigDig(b))
+const bigNumMinus = (a = 0, b = 0) => createOp(a, b, 'minus')
+const bigNumDiv = (a = 0, b = 1) => createOp(a, b, 'div')
+
+export function handleMouseMove ({ xMousePos, chartXStart, chartWidth, gasPrices, estimatedTimes, chart }) {
+ const { currentPosValue, newTimeEstimate } = getNewXandTimeEstimate({
+ xMousePos,
+ chartXStart,
+ chartWidth,
+ gasPrices,
+ estimatedTimes,
+ })
+
+ if (currentPosValue === null && newTimeEstimate === null) {
+ hideDataUI(chart, '#overlayed-circle')
+ return
+ }
+
+ const indexOfNewCircle = estimatedTimes.length + 1
+ const dataUIObj = generateDataUIObj(currentPosValue, indexOfNewCircle, newTimeEstimate)
+
+ chart.internal.overlayPoint(dataUIObj, indexOfNewCircle)
+ chart.internal.showTooltip([dataUIObj], d3.select('.c3-areas-data1')._groups[0])
+ chart.internal.showXGridFocus([dataUIObj])
+}
+
+export function getCoordinateData (selector) {
+ const node = d3.select(selector).node()
+ return node ? node.getBoundingClientRect() : {}
+}
+
+export function generateDataUIObj (x, index, value) {
+ return {
+ x,
+ value,
+ index,
+ id: 'data1',
+ name: 'data1',
+ }
+}
+
+export function handleChartUpdate ({ chart, gasPrices, newPrice, cssId }) {
+ const {
+ closestLowerValueIndex,
+ closestLowerValue,
+ closestHigherValueIndex,
+ closestHigherValue,
+ } = getAdjacentGasPrices({ gasPrices, priceToPosition: newPrice })
+
+ if (closestLowerValue && closestHigherValue) {
+ setSelectedCircle({
+ chart,
+ newPrice,
+ closestLowerValueIndex,
+ closestLowerValue,
+ closestHigherValueIndex,
+ closestHigherValue,
+ })
+ } else {
+ hideDataUI(chart, 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)
+ return {
+ closestLowerValueIndex,
+ closestHigherValueIndex,
+ closestHigherValue: gasPrices[closestHigherValueIndex],
+ closestLowerValue: gasPrices[closestLowerValueIndex],
+ }
+}
+
+export function extrapolateY ({ higherY = 0, lowerY = 0, higherX = 0, lowerX = 0, xForExtrapolation = 0 }) {
+ const slope = bigNumMinus(higherY, lowerY).div(bigNumMinus(higherX, lowerX))
+ const newTimeEstimate = slope.times(bigNumMinus(higherX, xForExtrapolation)).minus(newBigSigDig(higherY)).negated()
+
+ return newTimeEstimate.toNumber()
+}
+
+
+export function getNewXandTimeEstimate ({ xMousePos, chartXStart, chartWidth, gasPrices, estimatedTimes }) {
+ const chartMouseXPos = bigNumMinus(xMousePos, chartXStart)
+ const posPercentile = bigNumDiv(chartMouseXPos, chartWidth)
+
+ const currentPosValue = (bigNumMinus(gasPrices[gasPrices.length - 1], gasPrices[0]))
+ .times(newBigSigDig(posPercentile))
+ .plus(newBigSigDig(gasPrices[0]))
+ .toNumber()
+
+ const {
+ closestLowerValueIndex,
+ closestLowerValue,
+ closestHigherValueIndex,
+ closestHigherValue,
+ } = getAdjacentGasPrices({ gasPrices, priceToPosition: currentPosValue })
+
+ return !closestHigherValue || !closestLowerValue
+ ? {
+ currentPosValue: null,
+ newTimeEstimate: null,
+ }
+ : {
+ currentPosValue,
+ newTimeEstimate: extrapolateY({
+ higherY: estimatedTimes[closestHigherValueIndex],
+ lowerY: estimatedTimes[closestLowerValueIndex],
+ higherX: closestHigherValue,
+ lowerX: closestLowerValue,
+ xForExtrapolation: currentPosValue,
+ }),
+ }
+}
+
+export function hideDataUI (chart, dataNodeId) {
+ const overLayedCircle = d3.select(dataNodeId)
+ if (!overLayedCircle.empty()) {
+ overLayedCircle.remove()
+ }
+ d3.select('.c3-tooltip-container').style('display', 'none !important')
+ chart.internal.hideXGridFocus()
+}
+
+export function setTickPosition (axis, n, newPosition, secondNewPosition) {
+ const positionToShift = axis === 'y' ? 'x' : 'y'
+ const secondPositionToShift = axis === 'y' ? 'y' : 'x'
+ d3.select('#chart')
+ .select(`.c3-axis-${axis}`)
+ .selectAll('.tick')
+ .filter((d, i) => i === n)
+ .select('text')
+ .attr(positionToShift, 0)
+ .select('tspan')
+ .attr(positionToShift, newPosition)
+ .attr(secondPositionToShift, secondNewPosition || 0)
+ .style('visibility', 'visible')
+}
+
+export function appendOrUpdateCircle ({ data, itemIndex, cx, cy, cssId, appendOnly }) {
+ const circle = this.main
+ .select('.c3-selected-circles' + this.getTargetSelectorSuffix(data.id))
+ .selectAll(`.c3-selected-circle-${itemIndex}`)
+
+ if (appendOnly || circle.empty()) {
+ circle.data([data])
+ .enter().append('circle')
+ .attr('class', () => this.generateClass('c3-selected-circle', itemIndex))
+ .attr('id', cssId)
+ .attr('cx', cx)
+ .attr('cy', cy)
+ .attr('stroke', () => this.color(data))
+ .attr('r', 6)
+ } else {
+ circle.data([data])
+ .attr('cx', cx)
+ .attr('cy', cy)
+ }
+}
+
+export function setSelectedCircle ({
+ chart,
+ newPrice,
+ closestLowerValueIndex,
+ closestLowerValue,
+ closestHigherValueIndex,
+ closestHigherValue,
+}) {
+ const numberOfValues = chart.internal.data.xs.data1.length
+
+ const { x: lowerX, y: lowerY } = getCoordinateData(`.c3-circle-${closestLowerValueIndex}`)
+ let { x: higherX, y: higherY } = getCoordinateData(`.c3-circle-${closestHigherValueIndex}`)
+ let count = closestHigherValueIndex + 1
+
+ if (lowerX && higherX) {
+ while (lowerX === higherX) {
+ higherX = getCoordinateData(`.c3-circle-${count}`).x
+ higherY = getCoordinateData(`.c3-circle-${count}`).y
+ count++
+ }
+ }
+
+ const currentX = bigNumMinus(higherX, lowerX)
+ .times(bigNumMinus(newPrice, closestLowerValue))
+ .div(bigNumMinus(closestHigherValue, closestLowerValue))
+ .plus(newBigSigDig(lowerX))
+
+ const newTimeEstimate = extrapolateY({ higherY, lowerY, higherX, lowerX, xForExtrapolation: currentX })
+
+ chart.internal.selectPoint(
+ generateDataUIObj(currentX.toNumber(), numberOfValues, newTimeEstimate),
+ numberOfValues
+ )
+}
+
+
+export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimatedTimesMax) {
+ const gasPricesMaxPadded = gasPricesMax + 1
+ const chart = c3.generate({
+ size: {
+ height: 165,
+ },
+ transition: {
+ duration: 0,
+ },
+ padding: {left: 20, right: 15, top: 6, bottom: 10},
+ data: {
+ x: 'x',
+ columns: [
+ ['x', ...gasPrices],
+ ['data1', ...estimatedTimes],
+ ],
+ types: {
+ data1: 'area',
+ },
+ selection: {
+ enabled: false,
+ },
+ },
+ color: {
+ data1: '#259de5',
+ },
+ axis: {
+ x: {
+ min: gasPrices[0],
+ max: gasPricesMax,
+ tick: {
+ values: [Math.floor(gasPrices[0]), Math.ceil(gasPricesMax)],
+ outer: false,
+ format: function (val) { return val + ' GWEI' },
+ },
+ padding: {left: gasPricesMax / 50, right: gasPricesMax / 50},
+ label: {
+ text: 'Gas Price ($)',
+ position: 'outer-center',
+ },
+ },
+ y: {
+ padding: {top: 7, bottom: 7},
+ tick: {
+ values: [Math.floor(estimatedTimesMax * 0.05), Math.ceil(estimatedTimesMax * 0.97)],
+ outer: false,
+ },
+ label: {
+ text: 'Confirmation time (sec)',
+ position: 'outer-middle',
+ },
+ min: 0,
+ },
+ },
+ legend: {
+ show: false,
+ },
+ grid: {
+ x: {},
+ lines: {
+ front: false,
+ },
+ },
+ point: {
+ focus: {
+ expand: {
+ enabled: false,
+ r: 3.5,
+ },
+ },
+ },
+ tooltip: {
+ format: {
+ title: (v) => v.toPrecision(4),
+ },
+ contents: function (d) {
+ const titleFormat = this.config.tooltip_format_title
+ let text
+ d.forEach(el => {
+ if (el && (el.value || el.value === 0) && !text) {
+ text = "<table class='" + 'custom-tooltip' + "'>" + "<tr><th colspan='2'>" + titleFormat(el.x) + '</th></tr>'
+ }
+ })
+ return text + '</table>' + "<div class='tooltip-arrow'></div>"
+ },
+ position: function (data) {
+ if (d3.select('#overlayed-circle').empty()) {
+ return { top: -100, left: -100 }
+ }
+
+ const { x: circleX, y: circleY, width: circleWidth } = getCoordinateData('#overlayed-circle')
+ const { x: chartXStart, y: chartYStart } = getCoordinateData('.c3-chart')
+
+ // TODO: Confirm the below constants work with all data sets and screen sizes
+ const flipTooltip = circleY - circleWidth < chartYStart + 5
+
+ d3
+ .select('.tooltip-arrow')
+ .style('margin-top', flipTooltip ? '-16px' : '4px')
+
+ return {
+ top: bigNumMinus(circleY, chartYStart).minus(19).plus(flipTooltip ? circleWidth + 38 : 0).toNumber(),
+ left: bigNumMinus(circleX, chartXStart).plus(newBigSigDig(circleWidth)).minus(bigNumDiv(gasPricesMaxPadded, 50)).toNumber(),
+ }
+ },
+ show: true,
+ },
+ })
+
+ chart.internal.selectPoint = function (data, itemIndex = (data.index || 0)) {
+ const { x: chartXStart, y: chartYStart } = getCoordinateData('.c3-areas-data1')
+
+ d3.select('#set-circle').remove()
+
+ appendOrUpdateCircle.bind(this)({
+ data,
+ itemIndex,
+ cx: () => bigNumMinus(data.x, chartXStart).plus(11).toNumber(),
+ cy: () => bigNumMinus(data.value, chartYStart).plus(10).toNumber(),
+ cssId: 'set-circle',
+ appendOnly: true,
+ })
+ }
+
+ chart.internal.overlayPoint = function (data, itemIndex) {
+ appendOrUpdateCircle.bind(this)({
+ data,
+ itemIndex,
+ cx: this.circleX.bind(this),
+ cy: this.circleY.bind(this),
+ cssId: 'overlayed-circle',
+ })
+ }
+
+ chart.internal.showTooltip = function (selectedData, element) {
+ const dataToShow = selectedData.filter((d) => d && (d.value || d.value === 0))
+
+ if (dataToShow.length) {
+ this.tooltip.html(
+ this.config.tooltip_contents.call(this, selectedData, this.axis.getXAxisTickFormat(), this.getYFormat(), this.color)
+ ).style('display', 'flex')
+
+ // Get tooltip dimensions
+ const tWidth = this.tooltip.property('offsetWidth')
+ const tHeight = this.tooltip.property('offsetHeight')
+ const position = this.config.tooltip_position.call(this, dataToShow, tWidth, tHeight, element)
+ // Set tooltip
+ this.tooltip.style('top', position.top + 'px').style('left', position.left + 'px')
+ }
+ }
+
+ return chart
+}
diff --git a/ui/app/components/app/gas-customization/gas-price-chart/index.js b/ui/app/components/app/gas-customization/gas-price-chart/index.js
new file mode 100644
index 000000000..9895acb62
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-price-chart/index.js
@@ -0,0 +1 @@
+export { default } from './gas-price-chart.component'
diff --git a/ui/app/components/app/gas-customization/gas-price-chart/index.scss b/ui/app/components/app/gas-customization/gas-price-chart/index.scss
new file mode 100644
index 000000000..097543104
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-price-chart/index.scss
@@ -0,0 +1,132 @@
+.gas-price-chart {
+ display: flex;
+ position: relative;
+ justify-content: center;
+
+ &__root {
+ max-height: 154px;
+ max-width: 391px;
+ position: relative;
+ overflow: hidden;
+
+ @media screen and (max-width: $break-small) {
+ max-width: 326px;
+ }
+ }
+
+ .tick text, .c3-axis-x-label, .c3-axis-y-label {
+ font-family: Roboto;
+ font-style: normal;
+ font-weight: bold;
+ line-height: normal;
+ font-size: 8px;
+ text-align: center;
+ fill: #9A9CA6 !important;
+ }
+
+ .c3-tooltip-container {
+ display: flex;
+ justify-content: center !important;
+ align-items: flex-end !important;
+ }
+
+ .custom-tooltip {
+ background: rgba(0, 0, 0, 1);
+ box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
+ border-radius: 3px;
+ opacity: 1 !important;
+ height: 21px;
+ z-index: 1;
+ }
+
+ .tooltip-arrow {
+ background: black;
+ box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.5);
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg);
+ opacity: 1 !important;
+ width: 9px;
+ height: 9px;
+ margin-top: 4px;
+ }
+
+ .custom-tooltip th {
+ font-family: Roboto;
+ font-style: normal;
+ font-weight: 500;
+ line-height: normal;
+ font-size: 10px;
+ text-align: center;
+ padding: 3px;
+ color: #FFFFFF;
+ }
+
+ .c3-circle {
+ visibility: hidden;
+ }
+
+ .c3-selected-circle, .c3-circle._expanded_ {
+ fill: #FFFFFF !important;
+ stroke-width: 2.4px !important;
+ stroke: #2d9fd9 !important;
+ /* visibility: visible; */
+ }
+
+ #set-circle {
+ fill: #313A5E !important;
+ stroke: #313A5E !important;
+ }
+
+ .c3-axis-x-label, .c3-axis-y-label {
+ font-weight: normal;
+ }
+
+ .tick text tspan {
+ visibility: hidden;
+ }
+
+ .c3-circle {
+ fill: #2d9fd9 !important;
+ }
+
+ .c3-line-data1 {
+ stroke: #2d9fd9 !important;
+ background: rgba(0,0,0,0) !important;
+ color: rgba(0,0,0,0) !important;
+ }
+
+ .c3 path {
+ fill: none;
+ }
+
+ .c3 path.c3-area-data1 {
+ opacity: 1;
+ fill: #e9edf1 !important;
+ }
+
+ .c3-xgrid-line line {
+ stroke: #B8B8B8 !important;
+ }
+
+ .c3-xgrid-focus {
+ stroke: #aaa;
+ }
+
+ .c3-axis-x .domain {
+ fill: none;
+ stroke: none;
+ }
+
+ .c3-axis-y .domain {
+ fill: none;
+ stroke: #C8CCD6;
+ }
+
+ .c3-event-rect {
+ cursor: pointer;
+ }
+}
+
+#chart {
+ background: #F8F9FB
+}
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
new file mode 100644
index 000000000..7dec7a85f
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js
@@ -0,0 +1,218 @@
+import React from 'react'
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+import sinon from 'sinon'
+import shallow from '../../../../../../lib/shallow-with-context'
+import * as d3 from 'd3'
+
+function timeout (time) {
+ return new Promise((resolve, reject) => {
+ setTimeout(resolve, time)
+ })
+}
+
+const propsMethodSpies = {
+ updateCustomGasPrice: sinon.spy(),
+}
+
+const selectReturnSpies = {
+ empty: sinon.spy(),
+ remove: sinon.spy(),
+ style: sinon.spy(),
+ select: d3.select,
+ attr: sinon.spy(),
+ on: sinon.spy(),
+ datum: sinon.stub().returns({ x: 'mockX' }),
+}
+
+const mockSelectReturn = {
+ ...d3.select('div'),
+ node: () => ({
+ getBoundingClientRect: () => ({ x: 123, y: 321, width: 400 }),
+ }),
+ ...selectReturnSpies,
+}
+
+const gasPriceChartUtilsSpies = {
+ appendOrUpdateCircle: sinon.spy(),
+ generateChart: sinon.stub().returns({ mockChart: true }),
+ generateDataUIObj: sinon.spy(),
+ getAdjacentGasPrices: sinon.spy(),
+ getCoordinateData: sinon.stub().returns({ x: 'mockCoordinateX', width: 'mockWidth' }),
+ getNewXandTimeEstimate: sinon.spy(),
+ handleChartUpdate: sinon.spy(),
+ hideDataUI: sinon.spy(),
+ setSelectedCircle: sinon.spy(),
+ setTickPosition: sinon.spy(),
+ handleMouseMove: sinon.spy(),
+}
+
+const testProps = {
+ gasPrices: [1.5, 2.5, 4, 8],
+ estimatedTimes: [100, 80, 40, 10],
+ gasPricesMax: 9,
+ estimatedTimesMax: '100',
+ currentPrice: 6,
+ updateCustomGasPrice: propsMethodSpies.updateCustomGasPrice,
+}
+
+const GasPriceChart = proxyquire('../gas-price-chart.component.js', {
+ './gas-price-chart.utils.js': gasPriceChartUtilsSpies,
+ 'd3': {
+ ...d3,
+ select: function (...args) {
+ const result = d3.select(...args)
+ return result.empty()
+ ? mockSelectReturn
+ : result
+ },
+ event: {
+ clientX: 'mockClientX',
+ },
+ },
+}).default
+
+sinon.spy(GasPriceChart.prototype, 'renderChart')
+
+describe('GasPriceChart Component', function () {
+ let wrapper
+
+ beforeEach(() => {
+ wrapper = shallow(<GasPriceChart {...testProps} />)
+ })
+
+ describe('render()', () => {
+ it('should render', () => {
+ assert(wrapper.hasClass('gas-price-chart'))
+ })
+
+ it('should render the chart div', () => {
+ assert(wrapper.childAt(0).hasClass('gas-price-chart__root'))
+ assert.equal(wrapper.childAt(0).props().id, 'chart')
+ })
+ })
+
+ describe('componentDidMount', () => {
+ it('should call this.renderChart with the components props', () => {
+ assert(GasPriceChart.prototype.renderChart.callCount, 1)
+ wrapper.instance().componentDidMount()
+ assert(GasPriceChart.prototype.renderChart.callCount, 2)
+ assert.deepEqual(GasPriceChart.prototype.renderChart.getCall(1).args, [{...testProps}])
+ })
+ })
+
+ describe('componentDidUpdate', () => {
+ it('should call handleChartUpdate if props.currentPrice has changed', () => {
+ gasPriceChartUtilsSpies.handleChartUpdate.resetHistory()
+ wrapper.instance().componentDidUpdate({ currentPrice: 7 })
+ assert.equal(gasPriceChartUtilsSpies.handleChartUpdate.callCount, 1)
+ })
+
+ it('should call handleChartUpdate with the correct props', () => {
+ gasPriceChartUtilsSpies.handleChartUpdate.resetHistory()
+ wrapper.instance().componentDidUpdate({ currentPrice: 7 })
+ assert.deepEqual(gasPriceChartUtilsSpies.handleChartUpdate.getCall(0).args, [{
+ chart: { mockChart: true },
+ gasPrices: [1.5, 2.5, 4, 8],
+ newPrice: 6,
+ cssId: '#set-circle',
+ }])
+ })
+
+ it('should not call handleChartUpdate if props.currentPrice has not changed', () => {
+ gasPriceChartUtilsSpies.handleChartUpdate.resetHistory()
+ wrapper.instance().componentDidUpdate({ currentPrice: 6 })
+ assert.equal(gasPriceChartUtilsSpies.handleChartUpdate.callCount, 0)
+ })
+ })
+
+ describe('renderChart', () => {
+ it('should call setTickPosition 4 times, with the expected props', async () => {
+ await timeout(0)
+ gasPriceChartUtilsSpies.setTickPosition.resetHistory()
+ assert.equal(gasPriceChartUtilsSpies.setTickPosition.callCount, 0)
+ wrapper.instance().renderChart(testProps)
+ await timeout(0)
+ assert.equal(gasPriceChartUtilsSpies.setTickPosition.callCount, 4)
+ assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(0).args, ['y', 0, -5, 8])
+ assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(1).args, ['y', 1, -3, -5])
+ assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(2).args, ['x', 0, 3])
+ assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(3).args, ['x', 1, 3, -8])
+ })
+
+ it('should call handleChartUpdate with the correct props', async () => {
+ await timeout(0)
+ gasPriceChartUtilsSpies.handleChartUpdate.resetHistory()
+ wrapper.instance().renderChart(testProps)
+ await timeout(0)
+ assert.deepEqual(gasPriceChartUtilsSpies.handleChartUpdate.getCall(0).args, [{
+ chart: { mockChart: true },
+ gasPrices: [1.5, 2.5, 4, 8],
+ newPrice: 6,
+ cssId: '#set-circle',
+ }])
+ })
+
+ it('should add three events to the chart', async () => {
+ await timeout(0)
+ selectReturnSpies.on.resetHistory()
+ assert.equal(selectReturnSpies.on.callCount, 0)
+ wrapper.instance().renderChart(testProps)
+ await timeout(0)
+ assert.equal(selectReturnSpies.on.callCount, 3)
+
+ const firstOnEventArgs = selectReturnSpies.on.getCall(0).args
+ assert.equal(firstOnEventArgs[0], 'mouseout')
+ const secondOnEventArgs = selectReturnSpies.on.getCall(1).args
+ assert.equal(secondOnEventArgs[0], 'click')
+ const thirdOnEventArgs = selectReturnSpies.on.getCall(2).args
+ assert.equal(thirdOnEventArgs[0], 'mousemove')
+ })
+
+ it('should hide the data UI on mouseout', async () => {
+ await timeout(0)
+ selectReturnSpies.on.resetHistory()
+ wrapper.instance().renderChart(testProps)
+ gasPriceChartUtilsSpies.hideDataUI.resetHistory()
+ await timeout(0)
+ const mouseoutEventArgs = selectReturnSpies.on.getCall(0).args
+ assert.equal(gasPriceChartUtilsSpies.hideDataUI.callCount, 0)
+ mouseoutEventArgs[1]()
+ assert.equal(gasPriceChartUtilsSpies.hideDataUI.callCount, 1)
+ assert.deepEqual(gasPriceChartUtilsSpies.hideDataUI.getCall(0).args, [{ mockChart: true }, '#overlayed-circle'])
+ })
+
+ it('should updateCustomGasPrice on click', async () => {
+ await timeout(0)
+ selectReturnSpies.on.resetHistory()
+ wrapper.instance().renderChart(testProps)
+ propsMethodSpies.updateCustomGasPrice.resetHistory()
+ await timeout(0)
+ const mouseoutEventArgs = selectReturnSpies.on.getCall(1).args
+ assert.equal(propsMethodSpies.updateCustomGasPrice.callCount, 0)
+ mouseoutEventArgs[1]()
+ assert.equal(propsMethodSpies.updateCustomGasPrice.callCount, 1)
+ assert.equal(propsMethodSpies.updateCustomGasPrice.getCall(0).args[0], 'mockX')
+ })
+
+ it('should handle mousemove', async () => {
+ await timeout(0)
+ selectReturnSpies.on.resetHistory()
+ wrapper.instance().renderChart(testProps)
+ gasPriceChartUtilsSpies.handleMouseMove.resetHistory()
+ await timeout(0)
+ const mouseoutEventArgs = selectReturnSpies.on.getCall(2).args
+ assert.equal(gasPriceChartUtilsSpies.handleMouseMove.callCount, 0)
+ mouseoutEventArgs[1]()
+ assert.equal(gasPriceChartUtilsSpies.handleMouseMove.callCount, 1)
+ assert.deepEqual(gasPriceChartUtilsSpies.handleMouseMove.getCall(0).args, [{
+ xMousePos: 'mockClientX',
+ chartXStart: 'mockCoordinateX',
+ chartWidth: 'mockWidth',
+ gasPrices: testProps.gasPrices,
+ estimatedTimes: testProps.estimatedTimes,
+ chart: { mockChart: true },
+ }])
+ })
+ })
+})
diff --git a/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js b/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js
new file mode 100644
index 000000000..5836e7dfc
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js
@@ -0,0 +1,48 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+
+export default class AdvancedTabContent extends Component {
+ static propTypes = {
+ onChange: PropTypes.func,
+ lowLabel: PropTypes.string,
+ highLabel: PropTypes.string,
+ value: PropTypes.number,
+ step: PropTypes.number,
+ max: PropTypes.number,
+ min: PropTypes.number,
+ }
+
+ render () {
+ const {
+ onChange,
+ lowLabel,
+ highLabel,
+ value,
+ step,
+ max,
+ min,
+ } = this.props
+
+ return (
+ <div className="gas-slider">
+ <input
+ className="gas-slider__input"
+ type="range"
+ step={step}
+ max={max}
+ min={min}
+ value={value}
+ id="gasSlider"
+ onChange={event => onChange(event.target.value)}
+ />
+ <div className="gas-slider__bar">
+ <div className="gas-slider__colored"/>
+ </div>
+ <div className="gas-slider__labels">
+ <span>{lowLabel}</span>
+ <span>{highLabel}</span>
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/app/gas-customization/gas-slider/index.js b/ui/app/components/app/gas-customization/gas-slider/index.js
new file mode 100644
index 000000000..f1752c93f
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-slider/index.js
@@ -0,0 +1 @@
+export { default } from './gas-slider.component'
diff --git a/ui/app/components/app/gas-customization/gas-slider/index.scss b/ui/app/components/app/gas-customization/gas-slider/index.scss
new file mode 100644
index 000000000..e6c734367
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas-slider/index.scss
@@ -0,0 +1,54 @@
+.gas-slider {
+ position: relative;
+ width: 322px;
+
+ &__input {
+ width: 322px;
+ margin-left: -2px;
+ z-index: 2;
+ }
+
+ input[type=range] {
+ -webkit-appearance: none !important;
+ }
+
+ input[type=range]::-webkit-slider-thumb {
+ -webkit-appearance: none !important;
+ height: 34px;
+ width: 34px;
+ background-color: $curious-blue;
+ box-shadow: 0 2px 4px 0 rgba(0,0,0,0.08);
+ border-radius: 50%;
+ position: relative;
+ z-index: 10;
+ }
+
+ &__bar {
+ height: 6px;
+ width: 322px;
+ background: $alto;
+ display: flex;
+ justify-content: space-between;
+ position: absolute;
+ top: 16px;
+ z-index: 0;
+ border-radius: 4px;
+ }
+
+ &__colored {
+ height: 6px;
+ border-radius: 4px;
+ margin-left: 102px;
+ width: 322px;
+ z-index: 1;
+ background-color: $blizzard-blue;
+ }
+
+ &__labels {
+ display: flex;
+ justify-content: space-between;
+ font-size: 12px;
+ margin-top: -6px;
+ color: $mid-gray;
+ }
+} \ No newline at end of file
diff --git a/ui/app/components/app/gas-customization/gas.selectors.js b/ui/app/components/app/gas-customization/gas.selectors.js
new file mode 100644
index 000000000..89374b5f1
--- /dev/null
+++ b/ui/app/components/app/gas-customization/gas.selectors.js
@@ -0,0 +1,14 @@
+const selectors = {
+ getCurrentBlockTime,
+ getBasicGasEstimateLoadingStatus,
+}
+
+module.exports = selectors
+
+function getCurrentBlockTime (state) {
+ return state.gas.currentBlockTime
+}
+
+function getBasicGasEstimateLoadingStatus (state) {
+ return state.gas.basicEstimateIsLoading
+}
diff --git a/ui/app/components/app/gas-customization/index.scss b/ui/app/components/app/gas-customization/index.scss
new file mode 100644
index 000000000..b06c1d044
--- /dev/null
+++ b/ui/app/components/app/gas-customization/index.scss
@@ -0,0 +1,7 @@
+@import './gas-slider/index';
+
+@import './gas-modal-page-container/index';
+
+@import './gas-price-chart/index';
+
+@import './advanced-gas-inputs/index';
diff --git a/ui/app/components/app/index.scss b/ui/app/components/app/index.scss
new file mode 100644
index 000000000..e9bb4ac9f
--- /dev/null
+++ b/ui/app/components/app/index.scss
@@ -0,0 +1,81 @@
+@import 'account-menu/index';
+
+@import 'add-token-button/index';
+
+@import 'app-header/index';
+
+@import '../ui/breadcrumbs/index';
+
+@import '../ui/button-group/index';
+
+@import '../ui/card/index';
+
+@import 'confirm-page-container/index';
+
+@import '../ui/currency-input/index';
+
+@import '../ui/currency-display/index';
+
+@import '../ui/error-message/index';
+
+@import '../ui/export-text-container/index';
+
+@import '../ui/identicon/index';
+
+@import 'info-box/index';
+
+@import 'menu-bar/index';
+
+@import 'modal/index';
+
+@import 'modals/index';
+
+@import 'network-display/index';
+
+@import '../ui/page-container/index';
+
+@import '../../pages/index';
+
+@import 'provider-page-container/index';
+
+@import 'selected-account/index';
+
+@import '../ui/sender-to-recipient/index';
+
+@import '../ui/tabs/index';
+
+@import '../ui/token-balance/index';
+
+@import 'transaction-activity-log/index';
+
+@import 'transaction-breakdown/index';
+
+@import 'transaction-view/index';
+
+@import 'transaction-view-balance/index';
+
+@import 'transaction-list/index';
+
+@import 'transaction-list-item/index';
+
+@import 'transaction-list-item-details/index';
+
+@import 'transaction-status/index';
+
+@import 'app-header/index';
+
+@import 'sidebars/index';
+
+@import '../ui/unit-input/index';
+
+@import 'gas-customization/gas-modal-page-container/index';
+
+@import 'gas-customization/gas-modal-page-container/index';
+
+@import 'gas-customization/gas-modal-page-container/index';
+
+@import 'gas-customization/index';
+
+@import 'gas-customization/gas-price-button-group/index';
+
+@import 'ui-migration-annoucement/index';
diff --git a/ui/app/components/info-box/index.js b/ui/app/components/app/info-box/index.js
index 6110422ed..6110422ed 100644
--- a/ui/app/components/info-box/index.js
+++ b/ui/app/components/app/info-box/index.js
diff --git a/ui/app/components/info-box/index.scss b/ui/app/components/app/info-box/index.scss
index 8b5626d79..8b5626d79 100644
--- a/ui/app/components/info-box/index.scss
+++ b/ui/app/components/app/info-box/index.scss
diff --git a/ui/app/components/info-box/info-box.component.js b/ui/app/components/app/info-box/info-box.component.js
index 8688b8e8f..8688b8e8f 100644
--- a/ui/app/components/info-box/info-box.component.js
+++ b/ui/app/components/app/info-box/info-box.component.js
diff --git a/ui/app/components/input-number.js b/ui/app/components/app/input-number.js
index eec5e3740..8a6ec725c 100644
--- a/ui/app/components/input-number.js
+++ b/ui/app/components/app/input-number.js
@@ -6,7 +6,7 @@ const {
conversionGTE,
conversionLTE,
subtractCurrencies,
-} = require('../conversion-util')
+} = require('../../helpers/utils/conversion-util')
module.exports = InputNumber
diff --git a/ui/app/components/app/loading-network-screen/index.js b/ui/app/components/app/loading-network-screen/index.js
new file mode 100644
index 000000000..726b4b530
--- /dev/null
+++ b/ui/app/components/app/loading-network-screen/index.js
@@ -0,0 +1 @@
+export { default } from './loading-network-screen.container'
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
new file mode 100644
index 000000000..348a997c8
--- /dev/null
+++ b/ui/app/components/app/loading-network-screen/loading-network-screen.component.js
@@ -0,0 +1,138 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Spinner from '../../ui/spinner'
+import Button from '../../ui/button'
+
+export default class LoadingNetworkScreen extends PureComponent {
+ state = {
+ showErrorScreen: false,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ loadingMessage: PropTypes.string,
+ cancelTime: PropTypes.number,
+ provider: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+ providerId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ showNetworkDropdown: PropTypes.func,
+ setProviderArgs: PropTypes.array,
+ lastSelectedProvider: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+ setProviderType: PropTypes.func,
+ isLoadingNetwork: PropTypes.bool,
+ }
+
+ componentDidMount = () => {
+ this.cancelCallTimeout = setTimeout(this.cancelCall, this.props.cancelTime || 15000)
+ }
+
+ getConnectingLabel = function (loadingMessage) {
+ if (loadingMessage) {
+ return loadingMessage
+ }
+ const { provider, providerId } = this.props
+ const providerName = provider.type
+
+ let name
+
+ if (providerName === 'mainnet') {
+ name = this.context.t('connectingToMainnet')
+ } else if (providerName === 'ropsten') {
+ name = this.context.t('connectingToRopsten')
+ } else if (providerName === 'kovan') {
+ name = this.context.t('connectingToKovan')
+ } else if (providerName === 'rinkeby') {
+ name = this.context.t('connectingToRinkeby')
+ } else {
+ name = this.context.t('connectingTo', [providerId])
+ }
+
+ return name
+ }
+
+ renderMessage = () => {
+ return <span>{ this.getConnectingLabel(this.props.loadingMessage) }</span>
+ }
+
+ renderLoadingScreenContent = () => {
+ return <div className="loading-overlay__screen-content">
+ <Spinner color="#F7C06C" />
+ {this.renderMessage()}
+ </div>
+ }
+
+ renderErrorScreenContent = () => {
+ const { showNetworkDropdown, setProviderArgs, setProviderType } = this.props
+
+ return <div className="loading-overlay__error-screen">
+ <span className="loading-overlay__emoji">&#128542;</span>
+ <span>{ this.context.t('somethingWentWrong') }</span>
+ <div className="loading-overlay__error-buttons">
+ <Button
+ type="default"
+ onClick={() => {
+ window.clearTimeout(this.cancelCallTimeout)
+ showNetworkDropdown()
+ }}
+ >
+ { this.context.t('switchNetworks') }
+ </Button>
+
+ <Button
+ type="primary"
+ onClick={() => {
+ this.setState({ showErrorScreen: false })
+ setProviderType(...setProviderArgs)
+ window.clearTimeout(this.cancelCallTimeout)
+ this.cancelCallTimeout = setTimeout(this.cancelCall, this.props.cancelTime || 15000)
+ }}
+ >
+ { this.context.t('tryAgain') }
+ </Button>
+ </div>
+ </div>
+ }
+
+ cancelCall = () => {
+ const { isLoadingNetwork } = this.props
+
+ if (isLoadingNetwork) {
+ this.setState({ showErrorScreen: true })
+ }
+ }
+
+ componentDidUpdate = (prevProps) => {
+ const { provider } = this.props
+ const { provider: prevProvider } = prevProps
+ if (provider.type !== prevProvider.type) {
+ window.clearTimeout(this.cancelCallTimeout)
+ this.setState({ showErrorScreen: false })
+ this.cancelCallTimeout = setTimeout(this.cancelCall, this.props.cancelTime || 15000)
+ }
+ }
+
+ componentWillUnmount = () => {
+ window.clearTimeout(this.cancelCallTimeout)
+ }
+
+ render () {
+ const { lastSelectedProvider, setProviderType } = this.props
+
+ return (
+ <div className="loading-overlay">
+ <div
+ className="page-container__header-close"
+ onClick={() => setProviderType(lastSelectedProvider || 'ropsten')}
+ />
+ <div className="loading-overlay__container">
+ { this.state.showErrorScreen
+ ? this.renderErrorScreenContent()
+ : this.renderLoadingScreenContent()
+ }
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/app/loading-network-screen/loading-network-screen.container.js b/ui/app/components/app/loading-network-screen/loading-network-screen.container.js
new file mode 100644
index 000000000..87f1397ce
--- /dev/null
+++ b/ui/app/components/app/loading-network-screen/loading-network-screen.container.js
@@ -0,0 +1,41 @@
+import { connect } from 'react-redux'
+import LoadingNetworkScreen from './loading-network-screen.component'
+import actions from '../../../store/actions'
+import { getNetworkIdentifier } from '../../../selectors/selectors'
+
+const mapStateToProps = state => {
+ const {
+ loadingMessage,
+ currentView,
+ } = state.appState
+ const {
+ provider,
+ lastSelectedProvider,
+ network,
+ } = state.metamask
+ const { rpcTarget, chainId, ticker, nickname, type } = provider
+
+ const setProviderArgs = type === 'rpc'
+ ? [rpcTarget, chainId, ticker, nickname]
+ : [provider.type]
+
+ return {
+ isLoadingNetwork: network === 'loading' && currentView.name !== 'config',
+ loadingMessage,
+ lastSelectedProvider,
+ setProviderArgs,
+ provider,
+ providerId: getNetworkIdentifier(state),
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ setProviderType: (type) => {
+ dispatch(actions.setProviderType(type))
+ },
+ showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(LoadingNetworkScreen)
diff --git a/ui/app/components/menu-bar/index.js b/ui/app/components/app/menu-bar/index.js
index c5760847f..c5760847f 100644
--- a/ui/app/components/menu-bar/index.js
+++ b/ui/app/components/app/menu-bar/index.js
diff --git a/ui/app/components/menu-bar/index.scss b/ui/app/components/app/menu-bar/index.scss
index f699f4090..f699f4090 100644
--- a/ui/app/components/menu-bar/index.scss
+++ b/ui/app/components/app/menu-bar/index.scss
diff --git a/ui/app/components/app/menu-bar/menu-bar.component.js b/ui/app/components/app/menu-bar/menu-bar.component.js
new file mode 100644
index 000000000..e37fddda4
--- /dev/null
+++ b/ui/app/components/app/menu-bar/menu-bar.component.js
@@ -0,0 +1,79 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Tooltip from '../../ui/tooltip'
+import SelectedAccount from '../selected-account'
+import AccountDetailsDropdown from '../dropdowns/account-details-dropdown.js'
+
+export default class MenuBar extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ metricsEvent: PropTypes.func,
+ }
+
+ static propTypes = {
+ hideSidebar: PropTypes.func,
+ sidebarOpen: PropTypes.bool,
+ showSidebar: PropTypes.func,
+ }
+
+ state = { accountDetailsMenuOpen: false }
+
+ render () {
+ const { t } = this.context
+ const { sidebarOpen, hideSidebar, showSidebar } = this.props
+ const { accountDetailsMenuOpen } = this.state
+
+ return (
+ <div className="menu-bar">
+ <Tooltip
+ title={t('menu')}
+ position="bottom"
+ >
+ <div
+ className="fa fa-bars menu-bar__sidebar-button"
+ onClick={() => {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Home',
+ name: 'Opened Hamburger',
+ },
+ })
+ sidebarOpen ? hideSidebar() : showSidebar()
+ }}
+ />
+ </Tooltip>
+ <SelectedAccount />
+
+ <Tooltip
+ title={t('accountOptions')}
+ position="bottom"
+ >
+ <div
+ className="fa fa-ellipsis-h fa-lg menu-bar__open-in-browser"
+ onClick={() => {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Home',
+ name: 'Opened Account Options',
+ },
+ })
+ this.setState({ accountDetailsMenuOpen: true })
+ }}
+ >
+ </div>
+ </Tooltip>
+
+ {
+ accountDetailsMenuOpen && (
+ <AccountDetailsDropdown
+ className="menu-bar__account-details-dropdown"
+ onClose={() => this.setState({ accountDetailsMenuOpen: false })}
+ />
+ )
+ }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/menu-bar/menu-bar.container.js b/ui/app/components/app/menu-bar/menu-bar.container.js
index ae32882ae..059263ff3 100644
--- a/ui/app/components/menu-bar/menu-bar.container.js
+++ b/ui/app/components/app/menu-bar/menu-bar.container.js
@@ -1,13 +1,13 @@
import { connect } from 'react-redux'
+import { WALLET_VIEW_SIDEBAR } from '../sidebars/sidebar.constants'
import MenuBar from './menu-bar.component'
-import { showSidebar, hideSidebar } from '../../actions'
+import { showSidebar, hideSidebar } from '../../../store/actions'
const mapStateToProps = state => {
- const { appState: { sidebar: { isOpen }, isMascara } } = state
+ const { appState: { sidebar: { isOpen } } } = state
return {
sidebarOpen: isOpen,
- isMascara,
}
}
@@ -16,7 +16,7 @@ const mapDispatchToProps = dispatch => {
showSidebar: () => {
dispatch(showSidebar({
transitionName: 'sidebar-right',
- type: 'wallet-view',
+ type: WALLET_VIEW_SIDEBAR,
}))
},
hideSidebar: () => dispatch(hideSidebar()),
diff --git a/ui/app/components/menu-droppo.js b/ui/app/components/app/menu-droppo.js
index c80bee2be..c80bee2be 100644
--- a/ui/app/components/menu-droppo.js
+++ b/ui/app/components/app/menu-droppo.js
diff --git a/ui/app/components/modal/index.js b/ui/app/components/app/modal/index.js
index 58309abbe..58309abbe 100644
--- a/ui/app/components/modal/index.js
+++ b/ui/app/components/app/modal/index.js
diff --git a/ui/app/components/modal/index.scss b/ui/app/components/app/modal/index.scss
index 2beb14633..ec67d15fd 100644
--- a/ui/app/components/modal/index.scss
+++ b/ui/app/components/app/modal/index.scss
@@ -1,4 +1,4 @@
-@import './modal-content/index';
+@import 'modal-content/index';
.modal-container {
width: 100%;
diff --git a/ui/app/components/modal/modal-content/index.js b/ui/app/components/app/modal/modal-content/index.js
index 733cfb3b8..733cfb3b8 100644
--- a/ui/app/components/modal/modal-content/index.js
+++ b/ui/app/components/app/modal/modal-content/index.js
diff --git a/ui/app/components/modal/modal-content/index.scss b/ui/app/components/app/modal/modal-content/index.scss
index 560505b84..560505b84 100644
--- a/ui/app/components/modal/modal-content/index.scss
+++ b/ui/app/components/app/modal/modal-content/index.scss
diff --git a/ui/app/components/modal/modal-content/modal-content.component.js b/ui/app/components/app/modal/modal-content/modal-content.component.js
index ecec0ee5b..ecec0ee5b 100644
--- a/ui/app/components/modal/modal-content/modal-content.component.js
+++ b/ui/app/components/app/modal/modal-content/modal-content.component.js
diff --git a/ui/app/components/modal/modal-content/tests/modal-content.component.test.js b/ui/app/components/app/modal/modal-content/tests/modal-content.component.test.js
index 17af09f45..17af09f45 100644
--- a/ui/app/components/modal/modal-content/tests/modal-content.component.test.js
+++ b/ui/app/components/app/modal/modal-content/tests/modal-content.component.test.js
diff --git a/ui/app/components/modal/modal.component.js b/ui/app/components/app/modal/modal.component.js
index 2a75b559b..49e131b3c 100644
--- a/ui/app/components/modal/modal.component.js
+++ b/ui/app/components/app/modal/modal.component.js
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import Button from '../button'
+import Button from '../../ui/button'
export default class Modal extends PureComponent {
static propTypes = {
@@ -12,6 +12,7 @@ export default class Modal extends PureComponent {
onSubmit: PropTypes.func,
submitType: PropTypes.string,
submitText: PropTypes.string,
+ submitDisabled: PropTypes.bool,
// Cancel button (left button)
onCancel: PropTypes.func,
cancelType: PropTypes.string,
@@ -31,6 +32,7 @@ export default class Modal extends PureComponent {
onSubmit,
submitType,
submitText,
+ submitDisabled,
onCancel,
cancelType,
cancelText,
@@ -69,6 +71,7 @@ export default class Modal extends PureComponent {
<Button
type={submitType}
onClick={onSubmit}
+ disabled={submitDisabled}
className="modal-container__footer-button"
>
{ submitText }
diff --git a/ui/app/components/modal/tests/modal.component.test.js b/ui/app/components/app/modal/tests/modal.component.test.js
index 8cce1a808..a13d7c06a 100644
--- a/ui/app/components/modal/tests/modal.component.test.js
+++ b/ui/app/components/app/modal/tests/modal.component.test.js
@@ -1,9 +1,9 @@
import React from 'react'
import assert from 'assert'
-import { shallow } from 'enzyme'
+import { mount, shallow } from 'enzyme'
import sinon from 'sinon'
import Modal from '../modal.component'
-import Button from '../../button'
+import Button from '../../../ui/button'
describe('Modal Component', () => {
it('should render a modal with a submit button', () => {
@@ -100,4 +100,34 @@ describe('Modal Component', () => {
assert.equal(handleCancel.callCount, 1)
assert.equal(handleSubmit.callCount, 0)
})
+
+ it('should disable the submit button if submitDisabled is true', () => {
+ const handleCancel = sinon.spy()
+ const handleSubmit = sinon.spy()
+ const wrapper = mount(
+ <Modal
+ onCancel={handleCancel}
+ cancelText="Cancel"
+ onSubmit={handleSubmit}
+ submitText="Submit"
+ submitDisabled={true}
+ headerText="My Header"
+ onClose={handleCancel}
+ />
+ )
+
+ const buttons = wrapper.find(Button)
+ assert.equal(buttons.length, 2)
+ const cancelButton = buttons.at(0)
+ const submitButton = buttons.at(1)
+
+ assert.equal(handleCancel.callCount, 0)
+ cancelButton.simulate('click')
+ assert.equal(handleCancel.callCount, 1)
+
+ assert.equal(submitButton.props().disabled, true)
+ assert.equal(handleSubmit.callCount, 0)
+ submitButton.simulate('click')
+ assert.equal(handleSubmit.callCount, 0)
+ })
})
diff --git a/ui/app/components/modals/account-details-modal.js b/ui/app/components/app/modals/account-details-modal.js
index 248ffe008..94ed04df9 100644
--- a/ui/app/components/modals/account-details-modal.js
+++ b/ui/app/components/app/modals/account-details-modal.js
@@ -3,14 +3,14 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const actions = require('../../actions')
+const actions = require('../../../store/actions')
const AccountModalContainer = require('./account-modal-container')
-const { getSelectedIdentity } = require('../../selectors')
-const genAccountLink = require('../../../lib/account-link.js')
-const QrView = require('../qr-code')
-const EditableLabel = require('../editable-label')
+const { getSelectedIdentity } = require('../../../selectors/selectors')
+const genAccountLink = require('../../../../lib/account-link.js')
+const QrView = require('../../ui/qr-code')
+const EditableLabel = require('../../ui/editable-label')
-import Button from '../button'
+import Button from '../../ui/button'
function mapStateToProps (state) {
return {
@@ -77,6 +77,7 @@ AccountDetailsModal.prototype.render = function () {
h(QrView, {
Qr: {
data: address,
+ network: network,
},
}),
diff --git a/ui/app/components/modals/account-modal-container.js b/ui/app/components/app/modals/account-modal-container.js
index 2a6c655e1..b7ae0b5b8 100644
--- a/ui/app/components/modals/account-modal-container.js
+++ b/ui/app/components/app/modals/account-modal-container.js
@@ -3,9 +3,9 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const actions = require('../../actions')
-const { getSelectedIdentity } = require('../../selectors')
-import Identicon from '../identicon'
+const actions = require('../../../store/actions')
+const { getSelectedIdentity } = require('../../../selectors/selectors')
+import Identicon from '../../ui/identicon'
function mapStateToProps (state, ownProps) {
return {
diff --git a/ui/app/components/modals/buy-options-modal.js b/ui/app/components/app/modals/buy-options-modal.js
index c70510b5f..2df20e65c 100644
--- a/ui/app/components/modals/buy-options-modal.js
+++ b/ui/app/components/app/modals/buy-options-modal.js
@@ -3,8 +3,8 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const actions = require('../../actions')
-const { getNetworkDisplayName } = require('../../../../app/scripts/controllers/network/util')
+const actions = require('../../../store/actions')
+const { getNetworkDisplayName } = require('../../../../../app/scripts/controllers/network/util')
function mapStateToProps (state) {
return {
diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js b/ui/app/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js
index b973f221c..beebb7ed7 100644
--- a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js
+++ b/ui/app/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
-import { PRIMARY, SECONDARY } from '../../../../constants/common'
+import { PRIMARY, SECONDARY } from '../../../../../helpers/constants/common'
export default class CancelTransaction extends PureComponent {
static propTypes = {
diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.js b/ui/app/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/index.js
index 1a9ae2e07..1a9ae2e07 100644
--- a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.js
+++ b/ui/app/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/index.js
diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.scss b/ui/app/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/index.scss
index ce81dd448..ce81dd448 100644
--- a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.scss
+++ b/ui/app/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/index.scss
diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js b/ui/app/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js
index 014815503..014815503 100644
--- a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js
+++ b/ui/app/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js
diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js b/ui/app/components/app/modals/cancel-transaction/cancel-transaction.component.js
index 8b00cb9b9..6bab5ec1f 100644
--- a/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js
+++ b/ui/app/components/app/modals/cancel-transaction/cancel-transaction.component.js
@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Modal from '../../modal'
import CancelTransactionGasFee from './cancel-transaction-gas-fee'
-import { SUBMITTED_STATUS } from '../../../constants/transactions'
+import { SUBMITTED_STATUS } from '../../../../helpers/constants/transactions'
export default class CancelTransaction extends PureComponent {
static contextTypes = {
@@ -17,6 +17,10 @@ export default class CancelTransaction extends PureComponent {
newGasFee: PropTypes.string,
}
+ state = {
+ busy: false,
+ }
+
componentDidUpdate () {
const { transactionStatus, showTransactionConfirmedModal } = this.props
@@ -29,8 +33,10 @@ export default class CancelTransaction extends PureComponent {
handleSubmit = async () => {
const { createCancelTransaction, hideModal } = this.props
+ this.setState({ busy: true })
+
await createCancelTransaction()
- hideModal()
+ this.setState({ busy: false }, () => hideModal())
}
handleCancel = () => {
@@ -40,6 +46,7 @@ export default class CancelTransaction extends PureComponent {
render () {
const { t } = this.context
const { newGasFee } = this.props
+ const { busy } = this.state
return (
<Modal
@@ -50,6 +57,7 @@ export default class CancelTransaction extends PureComponent {
submitText={t('yesLetsTry')}
cancelText={t('nevermind')}
submitType="secondary"
+ submitDisabled={busy}
>
<div>
<div className="cancel-transaction__title">
diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction.container.js b/ui/app/components/app/modals/cancel-transaction/cancel-transaction.container.js
index eede8b1ee..6959889d9 100644
--- a/ui/app/components/modals/cancel-transaction/cancel-transaction.container.js
+++ b/ui/app/components/app/modals/cancel-transaction/cancel-transaction.container.js
@@ -1,11 +1,11 @@
import { connect } from 'react-redux'
import { compose } from 'recompose'
import ethUtil from 'ethereumjs-util'
-import { multiplyCurrencies } from '../../../conversion-util'
-import withModalProps from '../../../higher-order-components/with-modal-props'
+import { multiplyCurrencies } from '../../../../helpers/utils/conversion-util'
+import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
import CancelTransaction from './cancel-transaction.component'
-import { showModal, createCancelTransaction } from '../../../actions'
-import { getHexGasTotal } from '../../../helpers/confirm-transaction/util'
+import { showModal, createCancelTransaction } from '../../../../store/actions'
+import { getHexGasTotal } from '../../../../helpers/utils/confirm-tx.util'
const mapStateToProps = (state, ownProps) => {
const { metamask } = state
@@ -28,31 +28,29 @@ const mapStateToProps = (state, ownProps) => {
transactionId,
transactionStatus,
originalGasPrice,
+ defaultNewGasPrice,
newGasFee,
}
}
const mapDispatchToProps = dispatch => {
return {
- createCancelTransaction: txId => dispatch(createCancelTransaction(txId)),
+ createCancelTransaction: (txId, customGasPrice) => {
+ return dispatch(createCancelTransaction(txId, customGasPrice))
+ },
showTransactionConfirmedModal: () => dispatch(showModal({ name: 'TRANSACTION_CONFIRMED' })),
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
- const { transactionId, ...restStateProps } = stateProps
- const {
- createCancelTransaction: dispatchCreateCancelTransaction,
- ...restDispatchProps
- } = dispatchProps
+ const { transactionId, defaultNewGasPrice, ...restStateProps } = stateProps
+ const { createCancelTransaction, ...restDispatchProps } = dispatchProps
return {
...restStateProps,
...restDispatchProps,
...ownProps,
- createCancelTransaction: newGasPrice => {
- return dispatchCreateCancelTransaction(transactionId, newGasPrice)
- },
+ createCancelTransaction: () => createCancelTransaction(transactionId, defaultNewGasPrice),
}
}
diff --git a/ui/app/components/modals/cancel-transaction/index.js b/ui/app/components/app/modals/cancel-transaction/index.js
index 7abc871ee..7abc871ee 100644
--- a/ui/app/components/modals/cancel-transaction/index.js
+++ b/ui/app/components/app/modals/cancel-transaction/index.js
diff --git a/ui/app/components/modals/cancel-transaction/index.scss b/ui/app/components/app/modals/cancel-transaction/index.scss
index 62e8e36fd..4ffb5a0f8 100644
--- a/ui/app/components/modals/cancel-transaction/index.scss
+++ b/ui/app/components/app/modals/cancel-transaction/index.scss
@@ -1,4 +1,4 @@
-@import './cancel-transaction-gas-fee/index';
+@import 'cancel-transaction-gas-fee/index';
.cancel-transaction {
&__title {
@@ -15,4 +15,4 @@
&__cancel-transaction-gas-fee-container {
margin-bottom: 16px;
}
-} \ No newline at end of file
+}
diff --git a/ui/app/components/modals/cancel-transaction/tests/cancel-transaction.component.test.js b/ui/app/components/app/modals/cancel-transaction/tests/cancel-transaction.component.test.js
index 858fb01a8..345951b0f 100644
--- a/ui/app/components/modals/cancel-transaction/tests/cancel-transaction.component.test.js
+++ b/ui/app/components/app/modals/cancel-transaction/tests/cancel-transaction.component.test.js
@@ -34,6 +34,7 @@ describe('CancelTransaction Component', () => {
defaultNewGasPrice="0x3b9aca00"
createCancelTransaction={createCancelTransactionSpy}
hideModal={hideModalSpy}
+ showTransactionConfirmedModal={() => {}}
/>,
{ context: { t }}
)
diff --git a/ui/app/components/modals/clear-approved-origins/clear-approved-origins.component.js b/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js
index ceaa20a95..ceaa20a95 100644
--- a/ui/app/components/modals/clear-approved-origins/clear-approved-origins.component.js
+++ b/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js
diff --git a/ui/app/components/modals/clear-approved-origins/clear-approved-origins.container.js b/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.container.js
index 3a801a062..2276bc7e7 100644
--- a/ui/app/components/modals/clear-approved-origins/clear-approved-origins.container.js
+++ b/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.container.js
@@ -1,8 +1,8 @@
import { connect } from 'react-redux'
import { compose } from 'recompose'
-import withModalProps from '../../../higher-order-components/with-modal-props'
+import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
import ClearApprovedOriginsComponent from './clear-approved-origins.component'
-import { clearApprovedOrigins } from '../../../actions'
+import { clearApprovedOrigins } from '../../../../store/actions'
const mapDispatchToProps = dispatch => {
return {
diff --git a/ui/app/components/modals/clear-approved-origins/index.js b/ui/app/components/app/modals/clear-approved-origins/index.js
index b3e321995..b3e321995 100644
--- a/ui/app/components/modals/clear-approved-origins/index.js
+++ b/ui/app/components/app/modals/clear-approved-origins/index.js
diff --git a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js b/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js
index 195c55421..f35fb85a0 100644
--- a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js
+++ b/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js
@@ -1,9 +1,9 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Modal from '../../modal'
-import { addressSummary } from '../../../util'
-import Identicon from '../../identicon'
-import genAccountLink from '../../../../lib/account-link'
+import { addressSummary } from '../../../../helpers/utils/util'
+import Identicon from '../../../ui/identicon'
+import genAccountLink from '../../../../../lib/account-link'
export default class ConfirmRemoveAccount extends Component {
static propTypes = {
diff --git a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js b/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.container.js
index 45c6654ab..0a3cda5b6 100644
--- a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js
+++ b/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.container.js
@@ -1,8 +1,8 @@
import { connect } from 'react-redux'
import { compose } from 'recompose'
import ConfirmRemoveAccount from './confirm-remove-account.component'
-import withModalProps from '../../../higher-order-components/with-modal-props'
-import { removeAccount } from '../../../actions'
+import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
+import { removeAccount } from '../../../../store/actions'
const mapStateToProps = state => {
return {
diff --git a/ui/app/components/modals/confirm-remove-account/index.js b/ui/app/components/app/modals/confirm-remove-account/index.js
index ecb5f7790..ecb5f7790 100644
--- a/ui/app/components/modals/confirm-remove-account/index.js
+++ b/ui/app/components/app/modals/confirm-remove-account/index.js
diff --git a/ui/app/components/modals/confirm-remove-account/index.scss b/ui/app/components/app/modals/confirm-remove-account/index.scss
index 3be3a1967..3be3a1967 100644
--- a/ui/app/components/modals/confirm-remove-account/index.scss
+++ b/ui/app/components/app/modals/confirm-remove-account/index.scss
diff --git a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js b/ui/app/components/app/modals/confirm-reset-account/confirm-reset-account.component.js
index f1a4542ac..f1a4542ac 100644
--- a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js
+++ b/ui/app/components/app/modals/confirm-reset-account/confirm-reset-account.component.js
diff --git a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js b/ui/app/components/app/modals/confirm-reset-account/confirm-reset-account.container.js
index c8a7b8478..ffbd40d9d 100644
--- a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js
+++ b/ui/app/components/app/modals/confirm-reset-account/confirm-reset-account.container.js
@@ -1,8 +1,8 @@
import { connect } from 'react-redux'
import { compose } from 'recompose'
-import withModalProps from '../../../higher-order-components/with-modal-props'
+import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
import ConfirmResetAccount from './confirm-reset-account.component'
-import { resetAccount } from '../../../actions'
+import { resetAccount } from '../../../../store/actions'
const mapDispatchToProps = dispatch => {
return {
diff --git a/ui/app/components/modals/confirm-reset-account/index.js b/ui/app/components/app/modals/confirm-reset-account/index.js
index ca4d9c5bf..ca4d9c5bf 100644
--- a/ui/app/components/modals/confirm-reset-account/index.js
+++ b/ui/app/components/app/modals/confirm-reset-account/index.js
diff --git a/ui/app/components/modals/customize-gas/customize-gas.component.js b/ui/app/components/app/modals/customize-gas/customize-gas.component.js
index 3f526bd43..5db5c79e7 100644
--- a/ui/app/components/modals/customize-gas/customize-gas.component.js
+++ b/ui/app/components/app/modals/customize-gas/customize-gas.component.js
@@ -1,8 +1,9 @@
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 Button from '../../button'
+import Button from '../../../ui/button'
import {
getDecimalGasLimit,
@@ -14,6 +15,7 @@ import {
export default class CustomizeGas extends Component {
static contextTypes = {
t: PropTypes.func,
+ metricsEvent: PropTypes.func,
}
static propTypes = {
@@ -73,9 +75,9 @@ export default class CustomizeGas extends Component {
}
render () {
- const { t } = this.context
+ const { t, metricsEvent } = this.context
const { hideModal } = this.props
- const { gasPrice, gasLimit } = this.state
+ const { gasPrice, gasLimit, originalGasPrice, originalGasLimit } = this.state
const { valid, errorKey } = this.validate()
return (
@@ -128,7 +130,24 @@ export default class CustomizeGas extends Component {
<Button
type="primary"
className="customize-gas__save"
- onClick={() => this.handleSave()}
+ onClick={() => {
+ metricsEvent({
+ eventOpts: {
+ category: 'Activation',
+ action: 'userCloses',
+ name: 'closeCustomizeGas',
+ },
+ pageOpts: {
+ section: 'customizeGasModal',
+ component: 'customizeGasSaveButton',
+ },
+ customVariables: {
+ gasPriceChange: (new BigNumber(gasPrice)).minus(new BigNumber(originalGasPrice)).toString(10),
+ gasLimitChange: (new BigNumber(gasLimit)).minus(new BigNumber(originalGasLimit)).toString(10),
+ },
+ })
+ this.handleSave()
+ }}
style={{ marginRight: '10px' }}
disabled={!valid}
>
diff --git a/ui/app/components/modals/customize-gas/customize-gas.container.js b/ui/app/components/app/modals/customize-gas/customize-gas.container.js
index 46a799795..221881a8a 100644
--- a/ui/app/components/modals/customize-gas/customize-gas.container.js
+++ b/ui/app/components/app/modals/customize-gas/customize-gas.container.js
@@ -1,6 +1,6 @@
import { connect } from 'react-redux'
import CustomizeGas from './customize-gas.component'
-import { hideModal } from '../../../actions'
+import { hideModal } from '../../../../store/actions'
const mapStateToProps = state => {
const { appState: { modal: { modalState: { props } } } } = state
diff --git a/ui/app/components/modals/customize-gas/customize-gas.util.js b/ui/app/components/app/modals/customize-gas/customize-gas.util.js
index 6ba4a7705..e686183bd 100644
--- a/ui/app/components/modals/customize-gas/customize-gas.util.js
+++ b/ui/app/components/app/modals/customize-gas/customize-gas.util.js
@@ -1,5 +1,5 @@
import ethUtil from 'ethereumjs-util'
-import { conversionUtil } from '../../../conversion-util'
+import { conversionUtil } from '../../../../helpers/utils/conversion-util'
export function getDecimalGasLimit (hexGasLimit) {
return conversionUtil(hexGasLimit, {
diff --git a/ui/app/components/modals/customize-gas/index.js b/ui/app/components/app/modals/customize-gas/index.js
index 3a0ab7edc..3a0ab7edc 100644
--- a/ui/app/components/modals/customize-gas/index.js
+++ b/ui/app/components/app/modals/customize-gas/index.js
diff --git a/ui/app/components/modals/customize-gas/index.scss b/ui/app/components/app/modals/customize-gas/index.scss
index e10452691..e10452691 100644
--- a/ui/app/components/modals/customize-gas/index.scss
+++ b/ui/app/components/app/modals/customize-gas/index.scss
diff --git a/ui/app/components/modals/deposit-ether-modal.js b/ui/app/components/app/modals/deposit-ether-modal.js
index a64b3282e..def88f085 100644
--- a/ui/app/components/modals/deposit-ether-modal.js
+++ b/ui/app/components/app/modals/deposit-ether-modal.js
@@ -3,16 +3,16 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const actions = require('../../actions')
-const { getNetworkDisplayName } = require('../../../../app/scripts/controllers/network/util')
+const actions = require('../../../store/actions')
+const { getNetworkDisplayName } = require('../../../../../app/scripts/controllers/network/util')
const ShapeshiftForm = require('../shapeshift-form')
-import Button from '../button'
+import Button from '../../ui/button'
let DIRECT_DEPOSIT_ROW_TITLE
let DIRECT_DEPOSIT_ROW_TEXT
-let COINBASE_ROW_TITLE
-let COINBASE_ROW_TEXT
+let WYRE_ROW_TITLE
+let WYRE_ROW_TEXT
let SHAPESHIFT_ROW_TITLE
let SHAPESHIFT_ROW_TEXT
let FAUCET_ROW_TITLE
@@ -54,8 +54,8 @@ function DepositEtherModal (props, context) {
// need to set after i18n locale has loaded
DIRECT_DEPOSIT_ROW_TITLE = context.t('directDepositEther')
DIRECT_DEPOSIT_ROW_TEXT = context.t('directDepositEtherExplainer')
- COINBASE_ROW_TITLE = context.t('buyCoinbase')
- COINBASE_ROW_TEXT = context.t('buyCoinbaseExplainer')
+ WYRE_ROW_TITLE = context.t('buyWithWyre')
+ WYRE_ROW_TEXT = context.t('buyWithWyreDescription')
SHAPESHIFT_ROW_TITLE = context.t('depositShapeShift')
SHAPESHIFT_ROW_TEXT = context.t('depositShapeShiftExplainer')
FAUCET_ROW_TITLE = context.t('testFaucet')
@@ -183,13 +183,13 @@ DepositEtherModal.prototype.render = function () {
this.renderRow({
logo: h('div.deposit-ether-modal__logo', {
style: {
- backgroundImage: 'url(\'./images/coinbase logo.png\')',
+ backgroundImage: 'url(\'./images/wyre.svg\')',
height: '40px',
},
}),
- title: COINBASE_ROW_TITLE,
- text: COINBASE_ROW_TEXT,
- buttonLabel: this.context.t('continueToCoinbase'),
+ title: WYRE_ROW_TITLE,
+ text: WYRE_ROW_TEXT,
+ buttonLabel: this.context.t('continueToWyre'),
onButtonClick: () => toCoinbase(address),
hide: isTestNetwork || buyingWithShapeshift,
}),
diff --git a/ui/app/components/modals/edit-account-name-modal.js b/ui/app/components/app/modals/edit-account-name-modal.js
index edced8725..41a9862e9 100644
--- a/ui/app/components/modals/edit-account-name-modal.js
+++ b/ui/app/components/app/modals/edit-account-name-modal.js
@@ -3,8 +3,8 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const actions = require('../../actions')
-const { getSelectedAccount } = require('../../selectors')
+const actions = require('../../../store/actions')
+const { getSelectedAccount } = require('../../../selectors/selectors')
function mapStateToProps (state) {
return {
diff --git a/ui/app/components/modals/export-private-key-modal.js b/ui/app/components/app/modals/export-private-key-modal.js
index d3e3c9a56..639887d4c 100644
--- a/ui/app/components/modals/export-private-key-modal.js
+++ b/ui/app/components/app/modals/export-private-key-modal.js
@@ -5,13 +5,13 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const { stripHexPrefix } = require('ethereumjs-util')
-const actions = require('../../actions')
+const actions = require('../../../store/actions')
const AccountModalContainer = require('./account-modal-container')
-const { getSelectedIdentity } = require('../../selectors')
-const ReadOnlyInput = require('../readonly-input')
+const { getSelectedIdentity } = require('../../../selectors/selectors')
+const ReadOnlyInput = require('../../ui/readonly-input')
const copyToClipboard = require('copy-to-clipboard')
-const { checksumAddress } = require('../../util')
-import Button from '../button'
+const { checksumAddress } = require('../../../helpers/utils/util')
+import Button from '../../ui/button'
function mapStateToPropsFactory () {
let selectedIdentity = null
diff --git a/ui/app/components/modals/hide-token-confirmation-modal.js b/ui/app/components/app/modals/hide-token-confirmation-modal.js
index 43f3009a5..8a9a48fd2 100644
--- a/ui/app/components/modals/hide-token-confirmation-modal.js
+++ b/ui/app/components/app/modals/hide-token-confirmation-modal.js
@@ -3,8 +3,8 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const actions = require('../../actions')
-import Identicon from '../identicon'
+const actions = require('../../../store/actions')
+import Identicon from '../../ui/identicon'
function mapStateToProps (state) {
return {
diff --git a/ui/app/components/modals/index.js b/ui/app/components/app/modals/index.js
index 1db1d33d4..1db1d33d4 100644
--- a/ui/app/components/modals/index.js
+++ b/ui/app/components/app/modals/index.js
diff --git a/ui/app/components/app/modals/index.scss b/ui/app/components/app/modals/index.scss
new file mode 100644
index 000000000..09b0bb73c
--- /dev/null
+++ b/ui/app/components/app/modals/index.scss
@@ -0,0 +1,11 @@
+@import 'cancel-transaction/index';
+
+@import 'confirm-remove-account/index';
+
+@import 'customize-gas/index';
+
+@import 'qr-scanner/index';
+
+@import 'transaction-confirmed/index';
+
+@import 'metametrics-opt-in-modal/index';
diff --git a/ui/app/components/app/modals/loading-network-error/index.js b/ui/app/components/app/modals/loading-network-error/index.js
new file mode 100644
index 000000000..b3737458a
--- /dev/null
+++ b/ui/app/components/app/modals/loading-network-error/index.js
@@ -0,0 +1 @@
+export { default } from './loading-network-error.container'
diff --git a/ui/app/components/modals/welcome-beta/welcome-beta.component.js b/ui/app/components/app/modals/loading-network-error/loading-network-error.component.js
index ef1799164..44f71e4b2 100644
--- a/ui/app/components/modals/welcome-beta/welcome-beta.component.js
+++ b/ui/app/components/app/modals/loading-network-error/loading-network-error.component.js
@@ -2,29 +2,28 @@ import React from 'react'
import PropTypes from 'prop-types'
import Modal, { ModalContent } from '../../modal'
-const TransactionConfirmed = (props, context) => {
+const LoadingNetworkError = (props, context) => {
const { t } = context
const { hideModal } = props
return (
<Modal
onSubmit={() => hideModal()}
- submitText={t('ok')}
+ submitText={t('tryAgain')}
>
<ModalContent
- title={t('uiWelcome')}
- description={t('uiWelcomeMessage')}
+ description={'Oops! Something went wrong.'}
/>
</Modal>
)
}
-TransactionConfirmed.contextTypes = {
+LoadingNetworkError.contextTypes = {
t: PropTypes.func,
}
-TransactionConfirmed.propTypes = {
+LoadingNetworkError.propTypes = {
hideModal: PropTypes.func,
}
-export default TransactionConfirmed
+export default LoadingNetworkError
diff --git a/ui/app/components/app/modals/loading-network-error/loading-network-error.container.js b/ui/app/components/app/modals/loading-network-error/loading-network-error.container.js
new file mode 100644
index 000000000..38ea9b2ab
--- /dev/null
+++ b/ui/app/components/app/modals/loading-network-error/loading-network-error.container.js
@@ -0,0 +1,4 @@
+import LoadingNetworkError from './loading-network-error.component'
+import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
+
+export default withModalProps(LoadingNetworkError)
diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/index.js b/ui/app/components/app/modals/metametrics-opt-in-modal/index.js
new file mode 100644
index 000000000..47f946757
--- /dev/null
+++ b/ui/app/components/app/modals/metametrics-opt-in-modal/index.js
@@ -0,0 +1 @@
+export { default } from './metametrics-opt-in-modal.container'
diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/index.scss b/ui/app/components/app/modals/metametrics-opt-in-modal/index.scss
new file mode 100644
index 000000000..88b6d7a4d
--- /dev/null
+++ b/ui/app/components/app/modals/metametrics-opt-in-modal/index.scss
@@ -0,0 +1,30 @@
+.metametrics-opt-in-modal {
+ .metametrics-opt-in__main {
+ justify-content: center;
+ margin-left: 3%;
+ margin-right: 0%;
+ max-height: 75vh;
+
+ @media screen and (max-width: 575px) {
+ max-height: 100vh;
+ }
+ }
+
+
+ .metametrics-opt-in__title {
+ font-size: 38px;
+ }
+
+ .metametrics-opt-in__content {
+ padding-right: 6px;
+ }
+
+ .metametrics-opt-in__footer {
+ @media screen and (max-width: 575px) {
+ margin-top: 10px;
+ justify-content: center;
+ margin-left: 2%;
+ max-height: 520px;
+ }
+ }
+} \ No newline at end of file
diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js
new file mode 100644
index 000000000..0335991fc
--- /dev/null
+++ b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js
@@ -0,0 +1,141 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import PageContainerFooter from '../../../ui/page-container/page-container-footer'
+
+export default class MetaMetricsOptInModal extends Component {
+ static propTypes = {
+ setParticipateInMetaMetrics: PropTypes.func,
+ hideModal: PropTypes.func,
+ }
+
+ static contextTypes = {
+ metricsEvent: PropTypes.func,
+ }
+
+ render () {
+ const { metricsEvent } = this.context
+ const { setParticipateInMetaMetrics, hideModal } = this.props
+
+ return (
+ <div className="metametrics-opt-in metametrics-opt-in-modal">
+ <div className="metametrics-opt-in__main">
+ <div className="metametrics-opt-in__content">
+ <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>
+ <div className="metametrics-opt-in__body-graphic">
+ <img src="images/metrics-chart.svg" />
+ </div>
+ <div className="metametrics-opt-in__title">Help Us Improve MetaMask</div>
+ <div className="metametrics-opt-in__body">
+ <div className="metametrics-opt-in__description">
+ MetaMask would like to gather usage data to better understand how our users interact with the extension. This data
+ will be used to continually improve the usability and user experience of our product and the Ethereum ecosystem.
+ </div>
+ <div className="metametrics-opt-in__description">
+ MetaMask will..
+ </div>
+
+ <div className="metametrics-opt-in__committments">
+ <div className="metametrics-opt-in__row">
+ <i className="fa fa-check" />
+ <div className="metametrics-opt-in__row-description">
+ Always allow you to opt-out via Settings
+ </div>
+ </div>
+ <div className="metametrics-opt-in__row">
+ <i className="fa fa-check" />
+ <div className="metametrics-opt-in__row-description">
+ Send anonymized click & pageview events
+ </div>
+ </div>
+ <div className="metametrics-opt-in__row">
+ <i className="fa fa-check" />
+ <div className="metametrics-opt-in__row-description">
+ Maintain a public aggregate dashboard to educate the community
+ </div>
+ </div>
+ <div className="metametrics-opt-in__row metametrics-opt-in__break-row">
+ <i className="fa fa-times" />
+ <div className="metametrics-opt-in__row-description">
+ <span className="metametrics-opt-in__bold">Never</span> collect keys, addresses, transactions, balances, hashes, or any personal information
+ </div>
+ </div>
+ <div className="metametrics-opt-in__row">
+ <i className="fa fa-times" />
+ <div className="metametrics-opt-in__row-description">
+ <span className="metametrics-opt-in__bold">Never</span> collect your full IP address
+ </div>
+ </div>
+ <div className="metametrics-opt-in__row">
+ <i className="fa fa-times" />
+ <div className="metametrics-opt-in__row-description">
+ <span className="metametrics-opt-in__bold">Never</span> sell data for profit. Ever!
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className="metametrics-opt-in__bottom-text">
+ This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our <a
+ href="https://metamask.io/privacy.html"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ Privacy Policy here
+ </a>.
+ </div>
+ </div>
+ <div className="metametrics-opt-in__footer">
+ <PageContainerFooter
+ onCancel={() => {
+ setParticipateInMetaMetrics(false)
+ .then(() => {
+ metricsEvent({
+ eventOpts: {
+ category: 'Onboarding',
+ action: 'Metrics Option',
+ name: 'Metrics Opt Out',
+ },
+ isOptIn: true,
+ }, {
+ excludeMetaMetricsId: true,
+ })
+ hideModal()
+ })
+ }}
+ cancelText={'No Thanks'}
+ hideCancel={false}
+ onSubmit={() => {
+ setParticipateInMetaMetrics(true)
+ .then(() => {
+ metricsEvent({
+ eventOpts: {
+ category: 'Onboarding',
+ action: 'Metrics Option',
+ name: 'Metrics Opt In',
+ },
+ isOptIn: true,
+ })
+ hideModal()
+ })
+ }}
+ submitText={'I agree'}
+ submitButtonType={'confirm'}
+ disabled={false}
+ />
+ </div>
+ </div>
+ </div>
+ )
+ }
+}
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
new file mode 100644
index 000000000..83595281f
--- /dev/null
+++ b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js
@@ -0,0 +1,24 @@
+import { connect } from 'react-redux'
+import { compose } from 'recompose'
+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 { unapprovedTxCount } = ownProps
+
+ return {
+ unapprovedTxCount,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)),
+ }
+}
+
+export default compose(
+ withModalProps,
+ connect(mapStateToProps, mapDispatchToProps),
+)(MetaMetricsOptInModal)
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/app/modals/modal.js
index 5aff4f5e1..717f623af 100644
--- a/ui/app/components/modals/modal.js
+++ b/ui/app/components/app/modals/modal.js
@@ -3,10 +3,11 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const FadeModal = require('boron').FadeModal
-const actions = require('../../actions')
-const isMobileView = require('../../../lib/is-mobile-view')
-const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
-const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
+const actions = require('../../../store/actions')
+const { resetCustomData: resetCustomGasData } = require('../../../ducks/gas/gas.duck')
+const isMobileView = require('../../../../lib/is-mobile-view')
+const { getEnvironmentType } = require('../../../../../app/scripts/lib/util')
+const { ENVIRONMENT_TYPE_POPUP } = require('../../../../../app/scripts/lib/enums')
// Modal Components
const BuyOptions = require('./buy-options-modal')
@@ -17,18 +18,18 @@ const ExportPrivateKeyModal = require('./export-private-key-modal')
const NewAccountModal = require('./new-account-modal')
const ShapeshiftDepositTxModal = require('./shapeshift-deposit-tx-modal.js')
const HideTokenConfirmationModal = require('./hide-token-confirmation-modal')
-const CustomizeGasModal = require('../customize-gas-modal')
const NotifcationModal = require('./notification-modal')
const QRScanner = require('./qr-scanner')
import ConfirmRemoveAccount from './confirm-remove-account'
import ConfirmResetAccount from './confirm-reset-account'
import TransactionConfirmed from './transaction-confirmed'
-import ConfirmCustomizeGasModal from './customize-gas'
import CancelTransaction from './cancel-transaction'
-import WelcomeBeta from './welcome-beta'
+
+import MetaMetricsOptInModal from './metametrics-opt-in-modal'
import RejectTransactions from './reject-transactions'
import ClearApprovedOrigins from './clear-approved-origins'
+import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container'
const modalContainerBaseStyle = {
transform: 'translate3d(-50%, 0, 0px)',
@@ -122,7 +123,8 @@ const MODALS = {
display: 'flex',
},
laptopModalStyle: {
- width: '850px',
+ width: 'initial',
+ maxWidth: '850px',
top: 'calc(10% + 10px)',
left: '0',
right: '0',
@@ -200,8 +202,8 @@ const MODALS = {
},
},
- BETA_UI_NOTIFICATION_MODAL: {
- contents: h(WelcomeBeta),
+ CLEAR_APPROVED_ORIGINS: {
+ contents: h(ClearApprovedOrigins),
mobileModalStyle: {
...modalContainerMobileStyle,
},
@@ -213,13 +215,17 @@ const MODALS = {
},
},
- CLEAR_APPROVED_ORIGINS: {
- contents: h(ClearApprovedOrigins),
+ METAMETRICS_OPT_IN_MODAL: {
+ contents: h(MetaMetricsOptInModal),
mobileModalStyle: {
...modalContainerMobileStyle,
+ width: '100%',
+ height: '100%',
+ top: '0px',
},
laptopModalStyle: {
...modalContainerLaptopStyle,
+ top: '10%',
},
contentStyle: {
borderRadius: '8px',
@@ -243,6 +249,40 @@ const MODALS = {
},
},
+ GAS_PRICE_INFO_MODAL: {
+ contents: [
+ h(NotifcationModal, {
+ header: 'gasPriceNoDenom',
+ message: 'gasPriceInfoModalContent',
+ }),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
+ },
+ laptopModalStyle: {
+ width: '449px',
+ top: 'calc(33% + 45px)',
+ },
+ },
+
+ GAS_LIMIT_INFO_MODAL: {
+ contents: [
+ h(NotifcationModal, {
+ header: 'gasLimit',
+ message: 'gasLimitInfoModalContent',
+ }),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
+ },
+ laptopModalStyle: {
+ width: '449px',
+ top: 'calc(33% + 45px)',
+ },
+ },
+
CONFIRM_RESET_ACCOUNT: {
contents: h(ConfirmResetAccount),
mobileModalStyle: {
@@ -295,7 +335,7 @@ const MODALS = {
CUSTOMIZE_GAS: {
contents: [
- h(CustomizeGasModal),
+ h(ConfirmCustomizeGasModal),
],
mobileModalStyle: {
width: '100vw',
@@ -307,35 +347,20 @@ const MODALS = {
margin: '0 auto',
},
laptopModalStyle: {
- width: '720px',
- height: '377px',
+ width: 'auto',
+ height: '0px',
top: '80px',
+ left: '0px',
transform: 'none',
- left: '0',
- right: '0',
margin: '0 auto',
+ position: 'relative',
},
- },
-
- CONFIRM_CUSTOMIZE_GAS: {
- contents: h(ConfirmCustomizeGasModal),
- mobileModalStyle: {
- width: '100vw',
- height: '100vh',
- top: '0',
- transform: 'none',
- left: '0',
- right: '0',
- margin: '0 auto',
+ contentStyle: {
+ borderRadius: '8px',
},
- laptopModalStyle: {
- width: '720px',
- height: '377px',
- top: '80px',
- transform: 'none',
- left: '0',
- right: '0',
- margin: '0 auto',
+ customOnHideOpts: {
+ action: resetCustomGasData,
+ args: [],
},
},
@@ -412,8 +437,11 @@ function mapStateToProps (state) {
function mapDispatchToProps (dispatch) {
return {
- hideModal: () => {
+ hideModal: (customOnHideOpts) => {
dispatch(actions.hideModal())
+ if (customOnHideOpts && customOnHideOpts.action) {
+ dispatch(customOnHideOpts.action(...customOnHideOpts.args))
+ }
},
hideWarning: () => {
dispatch(actions.hideWarning())
@@ -445,7 +473,7 @@ Modal.prototype.render = function () {
if (modal.onHide) {
modal.onHide(this.props)
}
- this.onHide()
+ this.onHide(modal.customOnHideOpts)
},
ref: (ref) => {
this.modalRef = ref
@@ -467,11 +495,11 @@ Modal.prototype.componentWillReceiveProps = function (nextProps) {
}
}
-Modal.prototype.onHide = function () {
+Modal.prototype.onHide = function (customOnHideOpts) {
if (this.props.onHideCallback) {
this.props.onHideCallback()
}
- this.props.hideModal()
+ this.props.hideModal(customOnHideOpts)
}
Modal.prototype.hide = function () {
diff --git a/ui/app/components/modals/new-account-modal.js b/ui/app/components/app/modals/new-account-modal.js
index a66a3ed4a..27c81a701 100644
--- a/ui/app/components/modals/new-account-modal.js
+++ b/ui/app/components/app/modals/new-account-modal.js
@@ -2,7 +2,7 @@ const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
-const actions = require('../../actions')
+const actions = require('../../../store/actions')
class NewAccountModal extends Component {
constructor (props) {
diff --git a/ui/app/components/modals/notification-modal.js b/ui/app/components/app/modals/notification-modal.js
index 46a4c8a21..2d73b2cfa 100644
--- a/ui/app/components/modals/notification-modal.js
+++ b/ui/app/components/app/modals/notification-modal.js
@@ -2,7 +2,7 @@ const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
-const actions = require('../../actions')
+const actions = require('../../../store/actions')
class NotificationModal extends Component {
render () {
diff --git a/ui/app/components/modals/qr-scanner/index.js b/ui/app/components/app/modals/qr-scanner/index.js
index 470dee1f4..470dee1f4 100644
--- a/ui/app/components/modals/qr-scanner/index.js
+++ b/ui/app/components/app/modals/qr-scanner/index.js
diff --git a/ui/app/components/modals/qr-scanner/index.scss b/ui/app/components/app/modals/qr-scanner/index.scss
index 6fa81d51f..6fa81d51f 100644
--- a/ui/app/components/modals/qr-scanner/index.scss
+++ b/ui/app/components/app/modals/qr-scanner/index.scss
diff --git a/ui/app/components/modals/qr-scanner/qr-scanner.component.js b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js
index cb8d07d83..20915b5f9 100644
--- a/ui/app/components/modals/qr-scanner/qr-scanner.component.js
+++ b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js
@@ -2,9 +2,9 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { BrowserQRCodeReader } from '@zxing/library'
import adapter from 'webrtc-adapter' // eslint-disable-line import/no-nodejs-modules, no-unused-vars
-import Spinner from '../../spinner'
-import WebcamUtils from '../../../../lib/webcam-utils'
-import PageContainerFooter from '../../page-container/page-container-footer/page-container-footer.component'
+import Spinner from '../../../ui/spinner'
+import WebcamUtils from '../../../../../lib/webcam-utils'
+import PageContainerFooter from '../../../ui/page-container/page-container-footer/page-container-footer.component'
export default class QrScanner extends Component {
static propTypes = {
diff --git a/ui/app/components/modals/qr-scanner/qr-scanner.container.js b/ui/app/components/app/modals/qr-scanner/qr-scanner.container.js
index d0a35e03b..2210fbed2 100644
--- a/ui/app/components/modals/qr-scanner/qr-scanner.container.js
+++ b/ui/app/components/app/modals/qr-scanner/qr-scanner.container.js
@@ -1,10 +1,10 @@
import { connect } from 'react-redux'
import QrScanner from './qr-scanner.component'
-const { hideModal, qrCodeDetected, showQrScanner } = require('../../../actions')
+const { hideModal, qrCodeDetected, showQrScanner } = require('../../../../store/actions')
import {
SEND_ROUTE,
-} from '../../../routes'
+} from '../../../../helpers/constants/routes'
const mapStateToProps = state => {
return {
diff --git a/ui/app/components/modals/reject-transactions/index.js b/ui/app/components/app/modals/reject-transactions/index.js
index fcdc372b6..fcdc372b6 100644
--- a/ui/app/components/modals/reject-transactions/index.js
+++ b/ui/app/components/app/modals/reject-transactions/index.js
diff --git a/ui/app/components/modals/reject-transactions/index.scss b/ui/app/components/app/modals/reject-transactions/index.scss
index 753466883..753466883 100644
--- a/ui/app/components/modals/reject-transactions/index.scss
+++ b/ui/app/components/app/modals/reject-transactions/index.scss
diff --git a/ui/app/components/modals/reject-transactions/reject-transactions.component.js b/ui/app/components/app/modals/reject-transactions/reject-transactions.component.js
index 60b259bdc..60b259bdc 100644
--- a/ui/app/components/modals/reject-transactions/reject-transactions.component.js
+++ b/ui/app/components/app/modals/reject-transactions/reject-transactions.component.js
diff --git a/ui/app/components/modals/reject-transactions/reject-transactions.container.js b/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js
index 81e98d3ff..d2af05573 100644
--- a/ui/app/components/modals/reject-transactions/reject-transactions.container.js
+++ b/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js
@@ -1,7 +1,7 @@
import { connect } from 'react-redux'
import { compose } from 'recompose'
import RejectTransactionsModal from './reject-transactions.component'
-import withModalProps from '../../../higher-order-components/with-modal-props'
+import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
const mapStateToProps = (state, ownProps) => {
const { unapprovedTxCount } = ownProps
diff --git a/ui/app/components/modals/shapeshift-deposit-tx-modal.js b/ui/app/components/app/modals/shapeshift-deposit-tx-modal.js
index 242c7b89d..ada9430f7 100644
--- a/ui/app/components/modals/shapeshift-deposit-tx-modal.js
+++ b/ui/app/components/app/modals/shapeshift-deposit-tx-modal.js
@@ -2,8 +2,8 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const actions = require('../../actions')
-const QrView = require('../qr-code')
+const actions = require('../../../store/actions')
+const QrView = require('../../ui/qr-code')
const AccountModalContainer = require('./account-modal-container')
function mapStateToProps (state) {
diff --git a/ui/app/components/modals/transaction-confirmed/index.js b/ui/app/components/app/modals/transaction-confirmed/index.js
index 7776b969e..7776b969e 100644
--- a/ui/app/components/modals/transaction-confirmed/index.js
+++ b/ui/app/components/app/modals/transaction-confirmed/index.js
diff --git a/ui/app/components/modals/transaction-confirmed/index.scss b/ui/app/components/app/modals/transaction-confirmed/index.scss
index c97371fb6..c97371fb6 100644
--- a/ui/app/components/modals/transaction-confirmed/index.scss
+++ b/ui/app/components/app/modals/transaction-confirmed/index.scss
diff --git a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js b/ui/app/components/app/modals/transaction-confirmed/transaction-confirmed.component.js
index 0a98eb1a1..0a98eb1a1 100644
--- a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js
+++ b/ui/app/components/app/modals/transaction-confirmed/transaction-confirmed.component.js
diff --git a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js b/ui/app/components/app/modals/transaction-confirmed/transaction-confirmed.container.js
index d4e39681a..9089ec158 100644
--- a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js
+++ b/ui/app/components/app/modals/transaction-confirmed/transaction-confirmed.container.js
@@ -1,4 +1,4 @@
import TransactionConfirmed from './transaction-confirmed.component'
-import withModalProps from '../../../higher-order-components/with-modal-props'
+import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
export default withModalProps(TransactionConfirmed)
diff --git a/ui/app/components/network-display/index.js b/ui/app/components/app/network-display/index.js
index f6878ae5b..f6878ae5b 100644
--- a/ui/app/components/network-display/index.js
+++ b/ui/app/components/app/network-display/index.js
diff --git a/ui/app/components/network-display/index.scss b/ui/app/components/app/network-display/index.scss
index e9f2f2057..e9f2f2057 100644
--- a/ui/app/components/network-display/index.scss
+++ b/ui/app/components/app/network-display/index.scss
diff --git a/ui/app/components/network-display/network-display.component.js b/ui/app/components/app/network-display/network-display.component.js
index 22d617099..1142e8606 100644
--- a/ui/app/components/network-display/network-display.component.js
+++ b/ui/app/components/app/network-display/network-display.component.js
@@ -6,7 +6,7 @@ import {
ROPSTEN_CODE,
RINKEYBY_CODE,
KOVAN_CODE,
-} from '../../../../app/scripts/controllers/network/enums'
+} from '../../../../../app/scripts/controllers/network/enums'
const networkToClassHash = {
[MAINNET_CODE]: 'mainnet',
diff --git a/ui/app/components/network-display/network-display.container.js b/ui/app/components/app/network-display/network-display.container.js
index 99a14fff4..99a14fff4 100644
--- a/ui/app/components/network-display/network-display.container.js
+++ b/ui/app/components/app/network-display/network-display.container.js
diff --git a/ui/app/components/network.js b/ui/app/components/app/network.js
index 611aadb7b..e18404f42 100644
--- a/ui/app/components/network.js
+++ b/ui/app/components/app/network.js
@@ -23,33 +23,19 @@ Network.prototype.render = function () {
const props = this.props
const context = this.context
const networkNumber = props.network
- let providerName, providerNick
+ let providerName, providerNick, providerUrl
try {
providerName = props.provider.type
providerNick = props.provider.nickname || ''
+ providerUrl = props.provider.rpcTarget
} catch (e) {
providerName = null
}
- let iconName, hoverText
+ const providerId = providerNick || providerName || providerUrl || null
+ let iconName
+ let hoverText
- if (networkNumber === 'loading') {
- return h('span.pointer.network-indicator', {
- style: {
- display: 'flex',
- alignItems: 'center',
- flexDirection: 'row',
- },
- onClick: (event) => this.props.onClick(event),
- }, [
- h('img', {
- title: context.t('attemptingConnect'),
- style: {
- width: '27px',
- },
- src: 'images/loading.svg',
- }),
- ])
- } else if (providerName === 'mainnet') {
+ if (providerName === 'mainnet') {
hoverText = context.t('mainnet')
iconName = 'ethereum-network'
} else if (providerName === 'ropsten') {
@@ -65,8 +51,8 @@ Network.prototype.render = function () {
hoverText = context.t('rinkeby')
iconName = 'rinkeby-test-network'
} else {
- hoverText = context.t('unknownNetwork')
- iconName = 'unknown-private-network'
+ hoverText = providerId
+ iconName = 'private-network'
}
return (
@@ -92,6 +78,7 @@ Network.prototype.render = function () {
h(NetworkDropdownIcon, {
backgroundColor: '#038789', // $blue-lagoon
nonSelectBackgroundColor: '#15afb2',
+ loading: networkNumber === 'loading',
}),
h('.network-name', context.t('mainnet')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
@@ -101,6 +88,7 @@ Network.prototype.render = function () {
h(NetworkDropdownIcon, {
backgroundColor: '#e91550', // $crimson
nonSelectBackgroundColor: '#ec2c50',
+ loading: networkNumber === 'loading',
}),
h('.network-name', context.t('ropsten')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
@@ -110,6 +98,7 @@ Network.prototype.render = function () {
h(NetworkDropdownIcon, {
backgroundColor: '#690496', // $purple
nonSelectBackgroundColor: '#b039f3',
+ loading: networkNumber === 'loading',
}),
h('.network-name', context.t('kovan')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
@@ -119,13 +108,31 @@ Network.prototype.render = function () {
h(NetworkDropdownIcon, {
backgroundColor: '#ebb33f', // $tulip-tree
nonSelectBackgroundColor: '#ecb23e',
+ loading: networkNumber === 'loading',
}),
h('.network-name', context.t('rinkeby')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
default:
return h('.network-indicator', [
- h('i.fa.fa-question-circle.fa-lg', {
+ networkNumber === 'loading'
+ ? h('span.pointer.network-indicator', {
+ style: {
+ display: 'flex',
+ alignItems: 'center',
+ flexDirection: 'row',
+ },
+ 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)',
diff --git a/ui/app/components/notice.js b/ui/app/components/app/notice.js
index bb7e0814c..bb7e0814c 100644
--- a/ui/app/components/notice.js
+++ b/ui/app/components/app/notice.js
diff --git a/ui/app/components/provider-page-container/index.js b/ui/app/components/app/provider-page-container/index.js
index 927c35940..927c35940 100644
--- a/ui/app/components/provider-page-container/index.js
+++ b/ui/app/components/app/provider-page-container/index.js
diff --git a/ui/app/components/provider-page-container/index.scss b/ui/app/components/app/provider-page-container/index.scss
index 8d35ac179..8d35ac179 100644
--- a/ui/app/components/provider-page-container/index.scss
+++ b/ui/app/components/app/provider-page-container/index.scss
diff --git a/ui/app/components/provider-page-container/provider-page-container-content/index.js b/ui/app/components/app/provider-page-container/provider-page-container-content/index.js
index 73e491adc..73e491adc 100644
--- a/ui/app/components/provider-page-container/provider-page-container-content/index.js
+++ b/ui/app/components/app/provider-page-container/provider-page-container-content/index.js
diff --git a/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.component.js b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js
index 268db613f..0eb1d616a 100644
--- a/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.component.js
+++ b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'
import React, {PureComponent} from 'react'
-import Identicon from '../../identicon'
+import Identicon from '../../../ui/identicon'
export default class ProviderPageContainerContent extends PureComponent {
static propTypes = {
diff --git a/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.container.js b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js
index 3ea1ce20e..4dbdddd16 100644
--- a/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.container.js
+++ b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js
@@ -1,6 +1,6 @@
import { connect } from 'react-redux'
import ProviderPageContainerContent from './provider-page-container-content.component'
-import { getSelectedIdentity } from '../../../selectors'
+import { getSelectedIdentity } from '../../../../selectors/selectors'
const mapStateToProps = (state) => {
return {
diff --git a/ui/app/components/provider-page-container/provider-page-container-header/index.js b/ui/app/components/app/provider-page-container/provider-page-container-header/index.js
index 430627d3a..430627d3a 100644
--- a/ui/app/components/provider-page-container/provider-page-container-header/index.js
+++ b/ui/app/components/app/provider-page-container/provider-page-container-header/index.js
diff --git a/ui/app/components/provider-page-container/provider-page-container-header/provider-page-container-header.component.js b/ui/app/components/app/provider-page-container/provider-page-container-header/provider-page-container-header.component.js
index 41bf6c3dd..41bf6c3dd 100644
--- a/ui/app/components/provider-page-container/provider-page-container-header/provider-page-container-header.component.js
+++ b/ui/app/components/app/provider-page-container/provider-page-container-header/provider-page-container-header.component.js
diff --git a/ui/app/components/provider-page-container/provider-page-container.component.js b/ui/app/components/app/provider-page-container/provider-page-container.component.js
index 902733616..910def2a3 100644
--- a/ui/app/components/provider-page-container/provider-page-container.component.js
+++ b/ui/app/components/app/provider-page-container/provider-page-container.component.js
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'
import React, {PureComponent} from 'react'
-import { ProviderPageContainerContent, ProviderPageContainerHeader } from './'
-import { PageContainerFooter } from '../page-container'
+import { ProviderPageContainerContent, ProviderPageContainerHeader } from '.'
+import { PageContainerFooter } from '../../ui/page-container'
export default class ProviderPageContainer extends PureComponent {
static propTypes = {
@@ -10,20 +10,46 @@ export default class ProviderPageContainer extends PureComponent {
rejectProviderRequest: PropTypes.func.isRequired,
siteImage: PropTypes.string,
siteTitle: PropTypes.string.isRequired,
+ tabID: PropTypes.string.isRequired,
};
static contextTypes = {
t: PropTypes.func,
+ metricsEvent: PropTypes.func,
};
+ componentDidMount () {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Auth',
+ action: 'Connect',
+ name: 'Popup Opened',
+ },
+ })
+ }
+
onCancel = () => {
- const { origin, rejectProviderRequest } = this.props
- rejectProviderRequest(origin)
+ const { tabID, rejectProviderRequest } = this.props
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Auth',
+ action: 'Connect',
+ name: 'Canceled',
+ },
+ })
+ rejectProviderRequest(tabID)
}
onSubmit = () => {
- const { approveProviderRequest, origin } = this.props
- approveProviderRequest(origin)
+ const { approveProviderRequest, tabID } = this.props
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Auth',
+ action: 'Connect',
+ name: 'Confirmed',
+ },
+ })
+ approveProviderRequest(tabID)
}
render () {
diff --git a/ui/app/components/selected-account/index.js b/ui/app/components/app/selected-account/index.js
index eb342181f..eb342181f 100644
--- a/ui/app/components/selected-account/index.js
+++ b/ui/app/components/app/selected-account/index.js
diff --git a/ui/app/components/selected-account/index.scss b/ui/app/components/app/selected-account/index.scss
index 5339a228b..5339a228b 100644
--- a/ui/app/components/selected-account/index.scss
+++ b/ui/app/components/app/selected-account/index.scss
diff --git a/ui/app/components/selected-account/selected-account.component.js b/ui/app/components/app/selected-account/selected-account.component.js
index 4f98df9b6..5a3fa815f 100644
--- a/ui/app/components/selected-account/selected-account.component.js
+++ b/ui/app/components/app/selected-account/selected-account.component.js
@@ -1,9 +1,9 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import copyToClipboard from 'copy-to-clipboard'
-import { addressSlicer, checksumAddress } from '../../util'
+import { addressSlicer, checksumAddress } from '../../../helpers/utils/util'
-const Tooltip = require('../tooltip-v2.js').default
+const Tooltip = require('../../ui/tooltip-v2.js').default
class SelectedAccount extends Component {
state = {
@@ -17,12 +17,13 @@ class SelectedAccount extends Component {
static propTypes = {
selectedAddress: PropTypes.string,
selectedIdentity: PropTypes.object,
+ network: PropTypes.string,
}
render () {
const { t } = this.context
- const { selectedAddress, selectedIdentity } = this.props
- const checksummedAddress = checksumAddress(selectedAddress)
+ const { selectedAddress, selectedIdentity, network } = this.props
+ const checksummedAddress = checksumAddress(selectedAddress, network)
return (
<div className="selected-account">
diff --git a/ui/app/components/selected-account/selected-account.container.js b/ui/app/components/app/selected-account/selected-account.container.js
index f9e061d15..b5dbe74f3 100644
--- a/ui/app/components/selected-account/selected-account.container.js
+++ b/ui/app/components/app/selected-account/selected-account.container.js
@@ -1,12 +1,13 @@
import { connect } from 'react-redux'
import SelectedAccount from './selected-account.component'
-const selectors = require('../../selectors')
+const selectors = require('../../../selectors/selectors')
const mapStateToProps = state => {
return {
selectedAddress: selectors.getSelectedAddress(state),
selectedIdentity: selectors.getSelectedIdentity(state),
+ network: state.metamask.network,
}
}
diff --git a/ui/app/components/selected-account/tests/selected-account-component.test.js b/ui/app/components/app/selected-account/tests/selected-account-component.test.js
index 78a94d1c8..78a94d1c8 100644
--- a/ui/app/components/selected-account/tests/selected-account-component.test.js
+++ b/ui/app/components/app/selected-account/tests/selected-account-component.test.js
diff --git a/ui/app/components/send/README.md b/ui/app/components/app/send/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/README.md
+++ b/ui/app/components/app/send/README.md
diff --git a/ui/app/components/send/account-list-item/account-list-item-README.md b/ui/app/components/app/send/account-list-item/account-list-item-README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/account-list-item/account-list-item-README.md
+++ b/ui/app/components/app/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/components/app/send/account-list-item/account-list-item.component.js
new file mode 100644
index 000000000..18e77b4f9
--- /dev/null
+++ b/ui/app/components/app/send/account-list-item/account-list-item.component.js
@@ -0,0 +1,108 @@
+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'
+
+export default class AccountListItem extends Component {
+
+ static propTypes = {
+ account: PropTypes.object,
+ className: PropTypes.string,
+ conversionRate: PropTypes.number,
+ currentCurrency: PropTypes.string,
+ displayAddress: PropTypes.bool,
+ displayBalance: PropTypes.bool,
+ handleClick: PropTypes.func,
+ icon: PropTypes.node,
+ balanceIsCached: PropTypes.bool,
+ showFiat: PropTypes.bool,
+ };
+
+ static defaultProps = {
+ showFiat: true,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ };
+
+ render () {
+ const {
+ account,
+ className,
+ displayAddress = false,
+ displayBalance = true,
+ handleClick,
+ icon = null,
+ balanceIsCached,
+ showFiat,
+ } = this.props
+
+ const { name, address, balance } = account || {}
+
+ return (<div
+ className={`account-list-item ${className}`}
+ onClick={() => handleClick && handleClick({ name, address, balance })}
+ >
+
+ <div className="account-list-item__top-row">
+ <Identicon
+ address={address}
+ className="account-list-item__identicon"
+ diameter={18}
+ />
+
+ <div className="account-list-item__account-name">{ name || address }</div>
+
+ {icon && <div className="account-list-item__icon">{ icon }</div>}
+
+ </div>
+
+ {displayAddress && name && <div className="account-list-item__account-address">
+ { checksumAddress(address) }
+ </div>}
+
+ {
+ displayBalance && (
+ <Tooltip
+ position="left"
+ title={this.context.t('balanceOutdated')}
+ disabled={!balanceIsCached}
+ style={{
+ left: '-20px !important',
+ }}
+ >
+ <div className={classnames('account-list-item__account-balances', {
+ 'account-list-item__cached-balances': balanceIsCached,
+ })}>
+ <div className="account-list-item__primary-cached-container">
+ <UserPreferencedCurrencyDisplay
+ type={PRIMARY}
+ value={balance}
+ hideTitle={true}
+ />
+ {
+ balanceIsCached ? <span className="account-list-item__cached-star">*</span> : null
+ }
+ </div>
+ {
+ showFiat && (
+ <UserPreferencedCurrencyDisplay
+ type={SECONDARY}
+ value={balance}
+ hideTitle={true}
+ />
+ )
+ }
+ </div>
+ </Tooltip>
+ )
+ }
+
+ </div>)
+ }
+}
diff --git a/ui/app/components/send/account-list-item/account-list-item.container.js b/ui/app/components/app/send/account-list-item/account-list-item.container.js
index f8e73d923..bc9a60f49 100644
--- a/ui/app/components/send/account-list-item/account-list-item.container.js
+++ b/ui/app/components/app/send/account-list-item/account-list-item.container.js
@@ -4,14 +4,24 @@ import {
getCurrentCurrency,
getNativeCurrency,
} from '../send.selectors.js'
+import {
+ getIsMainnet,
+ isBalanceCached,
+ preferencesSelector,
+} from '../../../../selectors/selectors'
import AccountListItem from './account-list-item.component'
export default connect(mapStateToProps)(AccountListItem)
function mapStateToProps (state) {
+ const { showFiatInTestnets } = preferencesSelector(state)
+ const isMainnet = getIsMainnet(state)
+
return {
conversionRate: getConversionRate(state),
currentCurrency: getCurrentCurrency(state),
nativeCurrency: getNativeCurrency(state),
+ balanceIsCached: isBalanceCached(state),
+ showFiat: (isMainnet || !!showFiatInTestnets),
}
}
diff --git a/ui/app/components/send/account-list-item/index.js b/ui/app/components/app/send/account-list-item/index.js
index 907485cf7..907485cf7 100644
--- a/ui/app/components/send/account-list-item/index.js
+++ b/ui/app/components/app/send/account-list-item/index.js
diff --git a/ui/app/components/send/account-list-item/tests/account-list-item-component.test.js b/ui/app/components/app/send/account-list-item/tests/account-list-item-component.test.js
index 6ffc0b1c6..5df9f77d6 100644
--- a/ui/app/components/send/account-list-item/tests/account-list-item-component.test.js
+++ b/ui/app/components/app/send/account-list-item/tests/account-list-item-component.test.js
@@ -3,7 +3,7 @@ import assert from 'assert'
import { shallow } from 'enzyme'
import sinon from 'sinon'
import proxyquire from 'proxyquire'
-import Identicon from '../../../identicon'
+import Identicon from '../../../../ui/identicon'
import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
const utilsMethodStubs = {
@@ -11,7 +11,7 @@ const utilsMethodStubs = {
}
const AccountListItem = proxyquire('../account-list-item.component.js', {
- '../../../util': utilsMethodStubs,
+ '../../../../helpers/utils/util': utilsMethodStubs,
}).default
@@ -121,6 +121,20 @@ describe('AccountListItem Component', function () {
{
type: 'PRIMARY',
value: 'mockBalance',
+ hideTitle: true,
+ }
+ )
+ })
+
+ it('should only render one CurrencyDisplay if showFiat is false', () => {
+ wrapper.setProps({ showFiat: false, displayBalance: true })
+ assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 1)
+ assert.deepEqual(
+ wrapper.find(UserPreferencedCurrencyDisplay).at(0).props(),
+ {
+ type: 'PRIMARY',
+ value: 'mockBalance',
+ hideTitle: true,
}
)
})
@@ -129,5 +143,6 @@ describe('AccountListItem Component', function () {
wrapper.setProps({ displayBalance: false })
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 0)
})
+
})
})
diff --git a/ui/app/components/app/send/account-list-item/tests/account-list-item-container.test.js b/ui/app/components/app/send/account-list-item/tests/account-list-item-container.test.js
new file mode 100644
index 000000000..19a9a02d0
--- /dev/null
+++ b/ui/app/components/app/send/account-list-item/tests/account-list-item-container.test.js
@@ -0,0 +1,73 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+
+let mapStateToProps
+
+proxyquire('../account-list-item.container.js', {
+ 'react-redux': {
+ connect: (ms, md) => {
+ mapStateToProps = ms
+ return () => ({})
+ },
+ },
+ '../send.selectors.js': {
+ getConversionRate: () => `mockConversionRate`,
+ getCurrentCurrency: () => `mockCurrentCurrency`,
+ getNativeCurrency: () => `mockNativeCurrency`,
+ },
+ '../../../../selectors/selectors': {
+ isBalanceCached: () => `mockBalanceIsCached`,
+ preferencesSelector: ({ showFiatInTestnets }) => ({
+ showFiatInTestnets,
+ }),
+ getIsMainnet: ({ isMainnet }) => isMainnet,
+ },
+})
+
+describe('account-list-item container', () => {
+
+ describe('mapStateToProps()', () => {
+
+ it('should map the correct properties to props', () => {
+ assert.deepEqual(mapStateToProps({ isMainnet: true, showFiatInTestnets: false }), {
+ conversionRate: 'mockConversionRate',
+ currentCurrency: 'mockCurrentCurrency',
+ nativeCurrency: 'mockNativeCurrency',
+ balanceIsCached: 'mockBalanceIsCached',
+ showFiat: true,
+ })
+ })
+
+ it('should map the correct properties to props when in mainnet and showFiatInTestnet is true', () => {
+ assert.deepEqual(mapStateToProps({ isMainnet: true, showFiatInTestnets: true }), {
+ conversionRate: 'mockConversionRate',
+ currentCurrency: 'mockCurrentCurrency',
+ nativeCurrency: 'mockNativeCurrency',
+ balanceIsCached: 'mockBalanceIsCached',
+ showFiat: true,
+ })
+ })
+
+ it('should map the correct properties to props when not in mainnet and showFiatInTestnet is true', () => {
+ assert.deepEqual(mapStateToProps({ isMainnet: false, showFiatInTestnets: true }), {
+ conversionRate: 'mockConversionRate',
+ currentCurrency: 'mockCurrentCurrency',
+ nativeCurrency: 'mockNativeCurrency',
+ balanceIsCached: 'mockBalanceIsCached',
+ showFiat: true,
+ })
+ })
+
+ it('should map the correct properties to props when not in mainnet and showFiatInTestnet is false', () => {
+ assert.deepEqual(mapStateToProps({ isMainnet: false, showFiatInTestnets: false }), {
+ conversionRate: 'mockConversionRate',
+ currentCurrency: 'mockCurrentCurrency',
+ nativeCurrency: 'mockNativeCurrency',
+ balanceIsCached: 'mockBalanceIsCached',
+ showFiat: false,
+ })
+ })
+
+ })
+
+})
diff --git a/ui/app/components/send/index.js b/ui/app/components/app/send/index.js
index b5114babc..b5114babc 100644
--- a/ui/app/components/send/index.js
+++ b/ui/app/components/app/send/index.js
diff --git a/ui/app/components/send/send-content/index.js b/ui/app/components/app/send/send-content/index.js
index 891c17e6a..891c17e6a 100644
--- a/ui/app/components/send/send-content/index.js
+++ b/ui/app/components/app/send/send-content/index.js
diff --git a/ui/app/components/send/send-content/send-amount-row/README.md b/ui/app/components/app/send/send-content/send-amount-row/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/send-content/send-amount-row/README.md
+++ b/ui/app/components/app/send/send-content/send-amount-row/README.md
diff --git a/ui/app/components/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
index ceb620941..f17137c1e 100644
--- a/ui/app/components/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
@@ -11,11 +11,11 @@ export default class AmountMaxButton extends Component {
setAmountToMax: PropTypes.func,
setMaxModeTo: PropTypes.func,
tokenBalance: PropTypes.string,
- };
+ }
static contextTypes = {
t: PropTypes.func,
- };
+ }
setMaxAmount () {
const {
@@ -35,7 +35,12 @@ export default class AmountMaxButton extends Component {
}
onMaxClick = (event) => {
- const { setMaxModeTo } = this.props
+ 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)
diff --git a/ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
index 2d2ec42f7..16c5a0db5 100644
--- a/ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
+++ b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
@@ -10,11 +10,11 @@ import { calcMaxAmount } from './amount-max-button.utils.js'
import {
updateSendAmount,
setMaxModeTo,
-} from '../../../../../actions'
+} from '../../../../../../store/actions'
import AmountMaxButton from './amount-max-button.component'
import {
updateSendErrors,
-} from '../../../../../ducks/send.duck'
+} from '../../../../../../ducks/send/send.duck'
export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton)
diff --git a/ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js
index 69fec1994..69fec1994 100644
--- a/ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js
+++ b/ui/app/components/app/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/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
new file mode 100644
index 000000000..f4c8fad8a
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
@@ -0,0 +1,29 @@
+const {
+ multiplyCurrencies,
+ subtractCurrencies,
+} = require('../../../../../../helpers/utils/conversion-util')
+const ethUtil = require('ethereumjs-util')
+
+function calcMaxAmount ({ balance, gasTotal, selectedToken, tokenBalance }) {
+ const { decimals } = selectedToken || {}
+ const multiplier = Math.pow(10, Number(decimals || 0))
+
+ return selectedToken
+ ? multiplyCurrencies(
+ tokenBalance,
+ multiplier,
+ {
+ toNumericBase: 'hex',
+ multiplicandBase: 16,
+ }
+ )
+ : subtractCurrencies(
+ ethUtil.addHexPrefix(balance),
+ ethUtil.addHexPrefix(gasTotal),
+ { toNumericBase: 'hex' }
+ )
+}
+
+module.exports = {
+ calcMaxAmount,
+}
diff --git a/ui/app/components/send/send-content/send-amount-row/amount-max-button/index.js b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/index.js
index ee8271494..ee8271494 100644
--- a/ui/app/components/send/send-content/send-amount-row/amount-max-button/index.js
+++ b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/index.js
diff --git a/ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
index b04d3897f..b04d3897f 100644
--- a/ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
+++ b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
diff --git a/ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js
index 2cc00d6d6..f446e330c 100644
--- a/ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js
+++ b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js
@@ -29,8 +29,8 @@ 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 },
- '../../../../../actions': actionSpies,
- '../../../../../ducks/send.duck': duckActionSpies,
+ '../../../../../../store/actions': actionSpies,
+ '../../../../../../ducks/send/send.duck': duckActionSpies,
})
describe('amount-max-button container', () => {
diff --git a/ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js
index 655fe1969..655fe1969 100644
--- a/ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js
+++ b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js
diff --git a/ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js
index 816df6a12..1ee858f67 100644
--- a/ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js
+++ b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js
@@ -19,7 +19,7 @@ describe('amount-max-button utils', () => {
selectedToken: {
decimals: 10,
},
- tokenBalance: 100,
+ tokenBalance: '64',
}), 'e8d4a51000')
})
})
diff --git a/ui/app/components/send/send-content/send-amount-row/index.js b/ui/app/components/app/send/send-content/send-amount-row/index.js
index abc6852fe..abc6852fe 100644
--- a/ui/app/components/send/send-content/send-amount-row/index.js
+++ b/ui/app/components/app/send/send-content/send-amount-row/index.js
diff --git a/ui/app/components/send/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.component.js
index 0268376bf..e725e7eda 100644
--- a/ui/app/components/send/send-content/send-amount-row/send-amount-row.component.js
+++ b/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.component.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import SendRowWrapper from '../send-row-wrapper/'
-import AmountMaxButton from './amount-max-button/'
+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'
@@ -26,11 +26,11 @@ export default class SendAmountRow extends Component {
updateSendAmount: PropTypes.func,
updateSendAmountError: PropTypes.func,
updateGas: PropTypes.func,
- };
+ }
static contextTypes = {
t: PropTypes.func,
- };
+ }
validateAmount (amount) {
const {
@@ -58,7 +58,6 @@ export default class SendAmountRow extends Component {
if (selectedToken) {
updateGasFeeError({
- amount,
amountConversionRate,
balance,
conversionRate,
diff --git a/ui/app/components/send/send-content/send-amount-row/send-amount-row.container.js b/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.container.js
index 3504d1b73..0646355ab 100644
--- a/ui/app/components/send/send-content/send-amount-row/send-amount-row.container.js
+++ b/ui/app/components/app/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 '../../../../actions'
+} from '../../../../../store/actions'
import {
updateSendErrors,
-} from '../../../../ducks/send.duck'
+} from '../../../../../ducks/send/send.duck'
import SendAmountRow from './send-amount-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow)
@@ -45,10 +45,10 @@ function mapDispatchToProps (dispatch) {
setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
updateSendAmount: newAmount => dispatch(updateSendAmount(newAmount)),
updateGasFeeError: (amountDataObject) => {
- dispatch(updateSendErrors(getGasFeeErrorObject(amountDataObject)))
+ dispatch(updateSendErrors(getGasFeeErrorObject(amountDataObject)))
},
updateSendAmountError: (amountDataObject) => {
- dispatch(updateSendErrors(getAmountErrorObject(amountDataObject)))
+ dispatch(updateSendErrors(getAmountErrorObject(amountDataObject)))
},
}
}
diff --git a/ui/app/components/send/send-content/send-amount-row/send-amount-row.scss b/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/send-content/send-amount-row/send-amount-row.scss
+++ b/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.scss
diff --git a/ui/app/components/send/send-content/send-amount-row/send-amount-row.selectors.js b/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.selectors.js
index fb08c7ed7..fb08c7ed7 100644
--- a/ui/app/components/send/send-content/send-amount-row/send-amount-row.selectors.js
+++ b/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.selectors.js
diff --git a/ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-component.test.js
index 56e80cb83..14a71129f 100644
--- a/ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-component.test.js
+++ b/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-component.test.js
@@ -82,7 +82,6 @@ describe('SendAmountRow Component', function () {
assert.deepEqual(
propsMethodSpies.updateGasFeeError.getCall(0).args,
[{
- amount: 'someAmount',
amountConversionRate: 'mockAmountConversionRate',
balance: 'mockBalance',
conversionRate: 7,
diff --git a/ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-container.test.js b/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-container.test.js
index 52e351aee..6d20202b0 100644
--- a/ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-container.test.js
+++ b/ui/app/components/app/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 }),
},
- '../../../../actions': actionSpies,
- '../../../../ducks/send.duck': duckActionSpies,
+ '../../../../../store/actions': actionSpies,
+ '../../../../../ducks/send/send.duck': duckActionSpies,
})
describe('send-amount-row container', () => {
diff --git a/ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js b/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js
index 4672cb8a7..4672cb8a7 100644
--- a/ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js
+++ b/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js
diff --git a/ui/app/components/send/send-content/send-content.component.js b/ui/app/components/app/send/send-content/send-content.component.js
index 1b03ffd2b..2c09ceb19 100644
--- a/ui/app/components/send/send-content/send-content.component.js
+++ b/ui/app/components/app/send/send-content/send-content.component.js
@@ -1,11 +1,11 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import PageContainerContent from '../../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 PageContainerContent from '../../../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 SendToRow from './send-to-row'
export default class SendContent extends Component {
@@ -13,7 +13,7 @@ export default class SendContent extends Component {
updateGas: PropTypes.func,
scanQrCode: PropTypes.func,
showHexData: PropTypes.bool,
- };
+ }
updateGas = (updateData) => this.props.updateGas(updateData)
diff --git a/ui/app/components/send/send-content/send-dropdown-list/index.js b/ui/app/components/app/send/send-content/send-dropdown-list/index.js
index 04af6536c..04af6536c 100644
--- a/ui/app/components/send/send-content/send-dropdown-list/index.js
+++ b/ui/app/components/app/send/send-content/send-dropdown-list/index.js
diff --git a/ui/app/components/send/send-content/send-dropdown-list/send-dropdown-list.component.js b/ui/app/components/app/send/send-content/send-dropdown-list/send-dropdown-list.component.js
index bedac1259..0d026bc69 100644
--- a/ui/app/components/send/send-content/send-dropdown-list/send-dropdown-list.component.js
+++ b/ui/app/components/app/send/send-content/send-dropdown-list/send-dropdown-list.component.js
@@ -1,6 +1,6 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import AccountListItem from '../../account-list-item/'
+import AccountListItem from '../../account-list-item'
export default class SendDropdownList extends Component {
diff --git a/ui/app/components/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js b/ui/app/components/app/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js
index b92dd4dfe..b92dd4dfe 100644
--- a/ui/app/components/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js
+++ b/ui/app/components/app/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js
diff --git a/ui/app/components/send/send-content/send-from-row/index.js b/ui/app/components/app/send/send-content/send-from-row/index.js
index 0a79726b2..0a79726b2 100644
--- a/ui/app/components/send/send-content/send-from-row/index.js
+++ b/ui/app/components/app/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/components/app/send/send-content/send-from-row/send-from-row.component.js
new file mode 100644
index 000000000..dfa53e970
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-from-row/send-from-row.component.js
@@ -0,0 +1,27 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import SendRowWrapper from '../send-row-wrapper'
+import AccountListItem from '../../account-list-item'
+
+export default class SendFromRow extends Component {
+ static propTypes = {
+ from: PropTypes.object,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ render () {
+ const { t } = this.context
+ const { from } = this.props
+
+ return (
+ <SendRowWrapper label={`${t('from')}:`}>
+ <div className="send-v2__from-dropdown">
+ <AccountListItem account={from} />
+ </div>
+ </SendRowWrapper>
+ )
+ }
+}
diff --git a/ui/app/components/app/send/send-content/send-from-row/send-from-row.container.js b/ui/app/components/app/send/send-content/send-from-row/send-from-row.container.js
new file mode 100644
index 000000000..fe3ac9aa1
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-from-row/send-from-row.container.js
@@ -0,0 +1,11 @@
+import { connect } from 'react-redux'
+import { getSendFromObject } from '../../send.selectors.js'
+import SendFromRow from './send-from-row.component'
+
+function mapStateToProps (state) {
+ return {
+ from: getSendFromObject(state),
+ }
+}
+
+export default connect(mapStateToProps)(SendFromRow)
diff --git a/ui/app/components/send/send-content/send-from-row/send-from-row.selectors.js b/ui/app/components/app/send/send-content/send-from-row/send-from-row.selectors.js
index 03ef4806b..03ef4806b 100644
--- a/ui/app/components/send/send-content/send-from-row/send-from-row.selectors.js
+++ b/ui/app/components/app/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/components/app/send/send-content/send-from-row/tests/send-from-row-component.test.js
new file mode 100644
index 000000000..18811c57e
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-component.test.js
@@ -0,0 +1,31 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import SendFromRow from '../send-from-row.component.js'
+import AccountListItem from '../../../account-list-item'
+import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
+
+describe('SendFromRow Component', function () {
+ describe('render', () => {
+ const wrapper = shallow(
+ <SendFromRow
+ from={ { address: 'mockAddress' } }
+ />,
+ { context: { t: str => str + '_t' } }
+ )
+
+ it('should render a SendRowWrapper component', () => {
+ assert.equal(wrapper.find(SendRowWrapper).length, 1)
+ })
+
+ it('should pass the correct props to SendRowWrapper', () => {
+ const { label } = wrapper.find(SendRowWrapper).props()
+ assert.equal(label, 'from_t:')
+ })
+
+ it('should render the FromDropdown with the correct props', () => {
+ const { account } = wrapper.find(AccountListItem).props()
+ assert.deepEqual(account, { address: 'mockAddress' })
+ })
+ })
+})
diff --git a/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-container.test.js b/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-container.test.js
new file mode 100644
index 000000000..fd771ea77
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-container.test.js
@@ -0,0 +1,26 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+
+let mapStateToProps
+
+proxyquire('../send-from-row.container.js', {
+ 'react-redux': {
+ connect: ms => {
+ mapStateToProps = ms
+ return () => ({})
+ },
+ },
+ '../../send.selectors.js': {
+ getSendFromObject: (s) => `mockFrom:${s}`,
+ },
+})
+
+describe('send-from-row container', () => {
+ describe('mapStateToProps()', () => {
+ it('should map the correct properties to props', () => {
+ assert.deepEqual(mapStateToProps('mockState'), {
+ from: 'mockFrom:mockState',
+ })
+ })
+ })
+})
diff --git a/ui/app/components/send/send-content/send-from-row/tests/send-from-row-selectors.test.js b/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-selectors.test.js
index ecb57bbc3..ecb57bbc3 100644
--- a/ui/app/components/send/send-content/send-from-row/tests/send-from-row-selectors.test.js
+++ b/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-selectors.test.js
diff --git a/ui/app/components/send/send-content/send-gas-row/README.md b/ui/app/components/app/send/send-content/send-gas-row/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/send-content/send-gas-row/README.md
+++ b/ui/app/components/app/send/send-content/send-gas-row/README.md
diff --git a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js b/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js
index 9bbb67506..48088607a 100644
--- a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js
+++ b/ui/app/components/app/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 '../../../../../constants/common'
+import { PRIMARY, SECONDARY } from '../../../../../../helpers/constants/common'
export default class GasFeeDisplay extends Component {
@@ -11,7 +11,7 @@ export default class GasFeeDisplay extends Component {
convertedCurrency: PropTypes.string,
gasLoadingError: PropTypes.bool,
gasTotal: PropTypes.string,
- onClick: PropTypes.func,
+ onReset: PropTypes.func,
};
static contextTypes = {
@@ -19,7 +19,7 @@ export default class GasFeeDisplay extends Component {
};
render () {
- const { gasTotal, onClick, gasLoadingError } = this.props
+ const { gasTotal, gasLoadingError, onReset } = this.props
return (
<div className="send-v2__gas-fee-display">
@@ -46,11 +46,10 @@ export default class GasFeeDisplay extends Component {
</div>
}
<button
- className="sliders-icon-container"
- onClick={onClick}
- disabled={!gasTotal && !gasLoadingError}
+ className="gas-fee-reset"
+ onClick={onReset}
>
- <i className="fa fa-sliders sliders-icon" />
+ { this.context.t('reset') }
</button>
</div>
)
diff --git a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/index.js b/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/index.js
index dba0edb7b..dba0edb7b 100644
--- a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/index.js
+++ b/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/index.js
diff --git a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js b/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
index 9ff01493a..cb4180508 100644
--- a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
+++ b/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
@@ -8,18 +8,20 @@ import sinon from 'sinon'
const propsMethodSpies = {
showCustomizeGasModal: sinon.spy(),
+ onReset: sinon.spy(),
}
-describe('SendGasRow Component', function () {
+describe('GasFeeDisplay Component', function () {
let wrapper
beforeEach(() => {
wrapper = shallow(<GasFeeDisplay
conversionRate={20}
gasTotal={'mockGasTotal'}
- onClick={propsMethodSpies.showCustomizeGasModal}
primaryCurrency={'mockPrimaryCurrency'}
convertedCurrency={'mockConvertedCurrency'}
+ showGasButtonGroup={propsMethodSpies.showCustomizeGasModal}
+ onReset={propsMethodSpies.onReset}
/>, {context: {t: str => str + '_t'}})
})
@@ -41,13 +43,19 @@ describe('SendGasRow Component', function () {
assert.equal(value, 'mockGasTotal')
})
- it('should render the Button with the correct props', () => {
+ it('should render the reset button with the correct props', () => {
const {
onClick,
+ className,
} = wrapper.find('button').props()
- assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 0)
+ assert.equal(className, 'gas-fee-reset')
+ assert.equal(propsMethodSpies.onReset.callCount, 0)
onClick()
- assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 1)
+ assert.equal(propsMethodSpies.onReset.callCount, 1)
+ })
+
+ it('should render the reset button with the correct text', () => {
+ assert.equal(wrapper.find('button').text(), 'reset_t')
})
})
})
diff --git a/ui/app/components/send/send-content/send-gas-row/index.js b/ui/app/components/app/send/send-content/send-gas-row/index.js
index 3c7ff1d5f..3c7ff1d5f 100644
--- a/ui/app/components/send/send-content/send-gas-row/index.js
+++ b/ui/app/components/app/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/components/app/send/send-content/send-gas-row/send-gas-row.component.js
new file mode 100644
index 000000000..424a65b20
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.component.js
@@ -0,0 +1,131 @@
+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'
+
+export default class SendGasRow extends Component {
+
+ static propTypes = {
+ conversionRate: PropTypes.number,
+ convertedCurrency: PropTypes.string,
+ gasFeeError: PropTypes.bool,
+ gasLoadingError: PropTypes.bool,
+ gasTotal: PropTypes.string,
+ showCustomizeGasModal: PropTypes.func,
+ setGasPrice: PropTypes.func,
+ setGasLimit: PropTypes.func,
+ gasPriceButtonGroupProps: PropTypes.object,
+ gasButtonGroupShown: PropTypes.bool,
+ advancedInlineGasShown: PropTypes.bool,
+ resetGasButtons: PropTypes.func,
+ gasPrice: PropTypes.number,
+ gasLimit: PropTypes.number,
+ insufficientBalance: PropTypes.bool,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ metricsEvent: PropTypes.func,
+ };
+
+ renderAdvancedOptionsButton () {
+ const { metricsEvent } = this.context
+ const { showCustomizeGasModal } = this.props
+ return <div className="advanced-gas-options-btn" onClick={() => {
+ metricsEvent({
+ eventOpts: {
+ category: 'Transactions',
+ action: 'Edit Screen',
+ name: 'Clicked "Advanced Options"',
+ },
+ })
+ showCustomizeGasModal()
+ }}>
+ { this.context.t('advancedOptions') }
+ </div>
+ }
+
+ renderContent () {
+ const {
+ conversionRate,
+ convertedCurrency,
+ gasLoadingError,
+ gasTotal,
+ showCustomizeGasModal,
+ gasPriceButtonGroupProps,
+ gasButtonGroupShown,
+ advancedInlineGasShown,
+ resetGasButtons,
+ setGasPrice,
+ setGasLimit,
+ gasPrice,
+ gasLimit,
+ insufficientBalance,
+ } = this.props
+ const { metricsEvent } = this.context
+
+ const gasPriceButtonGroup = <div>
+ <GasPriceButtonGroup
+ className="gas-price-button-group--small"
+ showCheck={false}
+ {...gasPriceButtonGroupProps}
+ handleGasPriceSelection={(...args) => {
+ metricsEvent({
+ eventOpts: {
+ category: 'Transactions',
+ action: 'Edit Screen',
+ name: 'Changed Gas Button',
+ },
+ })
+ gasPriceButtonGroupProps.handleGasPriceSelection(...args)
+ }}
+ />
+ { this.renderAdvancedOptionsButton() }
+ </div>
+ const gasFeeDisplay = <GasFeeDisplay
+ conversionRate={conversionRate}
+ convertedCurrency={convertedCurrency}
+ gasLoadingError={gasLoadingError}
+ gasTotal={gasTotal}
+ onReset={resetGasButtons}
+ onClick={() => showCustomizeGasModal()}
+ />
+ const advancedGasInputs = <div>
+ <AdvancedGasInputs
+ updateCustomGasPrice={newGasPrice => setGasPrice(newGasPrice, gasLimit)}
+ updateCustomGasLimit={newGasLimit => setGasLimit(newGasLimit, gasPrice)}
+ customGasPrice={gasPrice}
+ customGasLimit={gasLimit}
+ insufficientBalance={insufficientBalance}
+ customPriceIsSafe={true}
+ isSpeedUp={false}
+ />
+ { this.renderAdvancedOptionsButton() }
+ </div>
+
+ if (advancedInlineGasShown) {
+ return advancedGasInputs
+ } else if (gasButtonGroupShown) {
+ return gasPriceButtonGroup
+ } else {
+ return gasFeeDisplay
+ }
+ }
+
+ render () {
+ const { gasFeeError } = this.props
+
+ return (
+ <SendRowWrapper
+ label={`${this.context.t('transactionFee')}:`}
+ showError={gasFeeError}
+ errorType={'gasFee'}
+ >
+ { this.renderContent() }
+ </SendRowWrapper>
+ )
+ }
+
+}
diff --git a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.container.js b/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.container.js
new file mode 100644
index 000000000..f81670c02
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.container.js
@@ -0,0 +1,118 @@
+import { connect } from 'react-redux'
+import {
+ getConversionRate,
+ getCurrentCurrency,
+ getGasTotal,
+ getGasPrice,
+ getGasLimit,
+ getSendAmount,
+} from '../../send.selectors.js'
+import {
+ isBalanceSufficient,
+ calcGasTotal,
+} from '../../send.utils.js'
+import {
+ getBasicGasEstimateLoadingStatus,
+ getRenderableEstimateDataForSmallButtonsFromGWEI,
+ getDefaultActiveButtonIndex,
+} from '../../../../../selectors/custom-gas'
+import {
+ showGasButtonGroup,
+} from '../../../../../ducks/send/send.duck'
+import {
+ resetCustomData,
+ setCustomGasPrice,
+ setCustomGasLimit,
+} 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 SendGasRow from './send-gas-row.component'
+
+export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(SendGasRow)
+
+function mapStateToProps (state) {
+ const gasButtonInfo = getRenderableEstimateDataForSmallButtonsFromGWEI(state)
+ const gasPrice = getGasPrice(state)
+ const gasLimit = getGasLimit(state)
+ const activeButtonIndex = getDefaultActiveButtonIndex(gasButtonInfo, gasPrice)
+
+ const gasTotal = getGasTotal(state)
+ const conversionRate = getConversionRate(state)
+ const balance = getCurrentEthBalance(state)
+
+ const insufficientBalance = !isBalanceSufficient({
+ amount: getSelectedToken(state) ? '0x0' : getSendAmount(state),
+ gasTotal,
+ balance,
+ conversionRate,
+ })
+
+ return {
+ conversionRate,
+ convertedCurrency: getCurrentCurrency(state),
+ gasTotal,
+ gasFeeError: gasFeeIsInError(state),
+ gasLoadingError: getGasLoadingError(state),
+ gasPriceButtonGroupProps: {
+ buttonDataLoading: getBasicGasEstimateLoadingStatus(state),
+ defaultActiveButtonIndex: 1,
+ newActiveButtonIndex: activeButtonIndex > -1 ? activeButtonIndex : null,
+ gasButtonInfo,
+ },
+ gasButtonGroupShown: getGasButtonGroupShown(state),
+ advancedInlineGasShown: getAdvancedInlineGasShown(state),
+ gasPrice,
+ gasLimit,
+ insufficientBalance,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ showCustomizeGasModal: () => dispatch(showModal({ name: 'CUSTOMIZE_GAS', hideBasic: true })),
+ setGasPrice: (newPrice, gasLimit) => {
+ dispatch(setGasPrice(newPrice))
+ dispatch(setCustomGasPrice(newPrice))
+ if (gasLimit) {
+ dispatch(setGasTotal(calcGasTotal(gasLimit, newPrice)))
+ }
+ },
+ setGasLimit: (newLimit, gasPrice) => {
+ dispatch(setGasLimit(newLimit))
+ dispatch(setCustomGasLimit(newLimit))
+ if (gasPrice) {
+ dispatch(setGasTotal(calcGasTotal(newLimit, gasPrice)))
+ }
+ },
+ showGasButtonGroup: () => dispatch(showGasButtonGroup()),
+ resetCustomData: () => dispatch(resetCustomData()),
+ }
+}
+
+function mergeProps (stateProps, dispatchProps, ownProps) {
+ const { gasPriceButtonGroupProps } = stateProps
+ const { gasButtonInfo } = gasPriceButtonGroupProps
+ const {
+ setGasPrice: dispatchSetGasPrice,
+ showGasButtonGroup: dispatchShowGasButtonGroup,
+ resetCustomData: dispatchResetCustomData,
+ ...otherDispatchProps
+ } = dispatchProps
+
+ return {
+ ...stateProps,
+ ...otherDispatchProps,
+ ...ownProps,
+ gasPriceButtonGroupProps: {
+ ...gasPriceButtonGroupProps,
+ handleGasPriceSelection: dispatchSetGasPrice,
+ },
+ resetGasButtons: () => {
+ dispatchResetCustomData()
+ dispatchSetGasPrice(gasButtonInfo[1].priceInHexWei)
+ dispatchShowGasButtonGroup()
+ },
+ setGasPrice: dispatchSetGasPrice,
+ }
+}
diff --git a/ui/app/components/send/send-content/send-gas-row/send-gas-row.scss b/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/send-content/send-gas-row/send-gas-row.scss
+++ b/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.scss
diff --git a/ui/app/components/send/send-content/send-gas-row/send-gas-row.selectors.js b/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.selectors.js
index 96f6293c2..79c838543 100644
--- a/ui/app/components/send/send-content/send-gas-row/send-gas-row.selectors.js
+++ b/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.selectors.js
@@ -1,6 +1,7 @@
const selectors = {
gasFeeIsInError,
getGasLoadingError,
+ getGasButtonGroupShown,
}
module.exports = selectors
@@ -12,3 +13,7 @@ function getGasLoadingError (state) {
function gasFeeIsInError (state) {
return Boolean(state.send.errors.gasFee)
}
+
+function getGasButtonGroupShown (state) {
+ return state.send.gasButtonGroupShown
+}
diff --git a/ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-component.test.js b/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-component.test.js
index 54a92bd2d..08f26854e 100644
--- a/ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-component.test.js
+++ b/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-component.test.js
@@ -6,9 +6,11 @@ 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'
const propsMethodSpies = {
showCustomizeGasModal: sinon.spy(),
+ resetGasButtons: sinon.spy(),
}
describe('SendGasRow Component', function () {
@@ -21,12 +23,18 @@ describe('SendGasRow Component', function () {
gasFeeError={'mockGasFeeError'}
gasLoadingError={false}
gasTotal={'mockGasTotal'}
+ gasButtonGroupShown={false}
showCustomizeGasModal={propsMethodSpies.showCustomizeGasModal}
- />, { context: { t: str => str + '_t' } })
+ resetGasButtons={propsMethodSpies.resetGasButtons}
+ gasPriceButtonGroupProps={{
+ someGasPriceButtonGroupProp: 'foo',
+ anotherGasPriceButtonGroupProp: 'bar',
+ }}
+ />, { context: { t: str => str + '_t', metricsEvent: () => ({}) } })
})
afterEach(() => {
- propsMethodSpies.showCustomizeGasModal.resetHistory()
+ propsMethodSpies.resetGasButtons.resetHistory()
})
describe('render', () => {
@@ -41,7 +49,7 @@ describe('SendGasRow Component', function () {
errorType,
} = wrapper.find(SendRowWrapper).props()
- assert.equal(label, 'gasFee_t:')
+ assert.equal(label, 'transactionFee_t:')
assert.equal(showError, 'mockGasFeeError')
assert.equal(errorType, 'gasFee')
})
@@ -56,14 +64,40 @@ describe('SendGasRow Component', function () {
convertedCurrency,
gasLoadingError,
gasTotal,
- onClick,
+ onReset,
} = wrapper.find(SendRowWrapper).childAt(0).props()
assert.equal(conversionRate, 20)
assert.equal(convertedCurrency, 'mockConvertedCurrency')
assert.equal(gasLoadingError, false)
assert.equal(gasTotal, 'mockGasTotal')
+ assert.equal(propsMethodSpies.resetGasButtons.callCount, 0)
+ onReset()
+ assert.equal(propsMethodSpies.resetGasButtons.callCount, 1)
+ })
+
+ it('should render the GasPriceButtonGroup if gasButtonGroupShown is true', () => {
+ wrapper.setProps({ gasButtonGroupShown: true })
+ const rendered = wrapper.find(SendRowWrapper).childAt(0)
+ assert.equal(rendered.children().length, 2)
+
+ const gasPriceButtonGroup = rendered.childAt(0)
+ assert(gasPriceButtonGroup.is(GasPriceButtonGroup))
+ assert(gasPriceButtonGroup.hasClass('gas-price-button-group--small'))
+ assert.equal(gasPriceButtonGroup.props().showCheck, false)
+ assert.equal(gasPriceButtonGroup.props().someGasPriceButtonGroupProp, 'foo')
+ assert.equal(gasPriceButtonGroup.props().anotherGasPriceButtonGroupProp, 'bar')
+ })
+
+ it('should render an advanced options button if gasButtonGroupShown is true', () => {
+ wrapper.setProps({ gasButtonGroupShown: true })
+ const rendered = wrapper.find(SendRowWrapper).childAt(0)
+ assert.equal(rendered.children().length, 2)
+
+ const advancedOptionsButton = rendered.childAt(1)
+ assert.equal(advancedOptionsButton.text(), 'advancedOptions_t')
+
assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 0)
- onClick()
+ advancedOptionsButton.props().onClick()
assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 1)
})
})
diff --git a/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-container.test.js b/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
new file mode 100644
index 000000000..d1f753639
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
@@ -0,0 +1,200 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+import sinon from 'sinon'
+
+let mapStateToProps
+let mapDispatchToProps
+let mergeProps
+
+const actionSpies = {
+ showModal: sinon.spy(),
+ setGasPrice: sinon.spy(),
+ setGasTotal: sinon.spy(),
+ setGasLimit: sinon.spy(),
+}
+
+const sendDuckSpies = {
+ showGasButtonGroup: sinon.spy(),
+}
+
+const gasDuckSpies = {
+ resetCustomData: sinon.spy(),
+ setCustomGasPrice: sinon.spy(),
+ setCustomGasLimit: sinon.spy(),
+}
+
+proxyquire('../send-gas-row.container.js', {
+ 'react-redux': {
+ connect: (ms, md, mp) => {
+ mapStateToProps = ms
+ mapDispatchToProps = md
+ mergeProps = mp
+ return () => ({})
+ },
+ },
+ '../../../../../selectors/selectors': {
+ getCurrentEthBalance: (s) => `mockCurrentEthBalance:${s}`,
+ getAdvancedInlineGasShown: (s) => `mockAdvancedInlineGasShown:${s}`,
+ getSelectedToken: () => false,
+ },
+ '../../send.selectors.js': {
+ getConversionRate: (s) => `mockConversionRate:${s}`,
+ getCurrentCurrency: (s) => `mockConvertedCurrency:${s}`,
+ getGasTotal: (s) => `mockGasTotal:${s}`,
+ getGasPrice: (s) => `mockGasPrice:${s}`,
+ getGasLimit: (s) => `mockGasLimit:${s}`,
+ getSendAmount: (s) => `mockSendAmount:${s}`,
+ },
+ '../../send.utils.js': {
+ isBalanceSufficient: ({
+ amount,
+ gasTotal,
+ balance,
+ conversionRate,
+ }) => `${amount}:${gasTotal}:${balance}:${conversionRate}`,
+ calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice,
+ },
+ './send-gas-row.selectors.js': {
+ getGasLoadingError: (s) => `mockGasLoadingError:${s}`,
+ gasFeeIsInError: (s) => `mockGasFeeError:${s}`,
+ getGasButtonGroupShown: (s) => `mockGetGasButtonGroupShown:${s}`,
+ },
+ '../../../../../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,
+})
+
+describe('send-gas-row container', () => {
+
+ describe('mapStateToProps()', () => {
+
+ it('should map the correct properties to props', () => {
+ assert.deepEqual(mapStateToProps('mockState'), {
+ conversionRate: 'mockConversionRate:mockState',
+ convertedCurrency: 'mockConvertedCurrency:mockState',
+ gasTotal: 'mockGasTotal:mockState',
+ gasFeeError: 'mockGasFeeError:mockState',
+ gasLoadingError: 'mockGasLoadingError:mockState',
+ gasPriceButtonGroupProps: {
+ buttonDataLoading: `mockBasicGasEstimateLoadingStatus:mockState`,
+ defaultActiveButtonIndex: 1,
+ newActiveButtonIndex: 49,
+ gasButtonInfo: `mockGasButtonInfo:mockState`,
+ },
+ gasButtonGroupShown: `mockGetGasButtonGroupShown:mockState`,
+ advancedInlineGasShown: 'mockAdvancedInlineGasShown:mockState',
+ gasLimit: 'mockGasLimit:mockState',
+ gasPrice: 'mockGasPrice:mockState',
+ insufficientBalance: false,
+ })
+ })
+
+ })
+
+ describe('mapDispatchToProps()', () => {
+ let dispatchSpy
+ let mapDispatchToPropsObject
+
+ beforeEach(() => {
+ dispatchSpy = sinon.spy()
+ mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
+ actionSpies.setGasTotal.resetHistory()
+ })
+
+ describe('showCustomizeGasModal()', () => {
+ it('should dispatch an action', () => {
+ mapDispatchToPropsObject.showCustomizeGasModal()
+ assert(dispatchSpy.calledOnce)
+ assert.deepEqual(
+ actionSpies.showModal.getCall(0).args[0],
+ { name: 'CUSTOMIZE_GAS', hideBasic: true }
+ )
+ })
+ })
+
+ describe('setGasPrice()', () => {
+ it('should dispatch an action', () => {
+ mapDispatchToPropsObject.setGasPrice('mockNewPrice', 'mockLimit')
+ assert(dispatchSpy.calledThrice)
+ assert(actionSpies.setGasPrice.calledOnce)
+ assert.equal(actionSpies.setGasPrice.getCall(0).args[0], 'mockNewPrice')
+ assert.equal(gasDuckSpies.setCustomGasPrice.getCall(0).args[0], 'mockNewPrice')
+ assert(actionSpies.setGasTotal.calledOnce)
+ assert.equal(actionSpies.setGasTotal.getCall(0).args[0], 'mockLimitmockNewPrice')
+ })
+ })
+
+ describe('setGasLimit()', () => {
+ it('should dispatch an action', () => {
+ mapDispatchToPropsObject.setGasLimit('mockNewLimit', 'mockPrice')
+ assert(dispatchSpy.calledThrice)
+ assert(actionSpies.setGasLimit.calledOnce)
+ assert.equal(actionSpies.setGasLimit.getCall(0).args[0], 'mockNewLimit')
+ assert.equal(gasDuckSpies.setCustomGasLimit.getCall(0).args[0], 'mockNewLimit')
+ assert(actionSpies.setGasTotal.calledOnce)
+ assert.equal(actionSpies.setGasTotal.getCall(0).args[0], 'mockNewLimitmockPrice')
+ })
+ })
+
+ describe('showGasButtonGroup()', () => {
+ it('should dispatch an action', () => {
+ mapDispatchToPropsObject.showGasButtonGroup()
+ assert(dispatchSpy.calledOnce)
+ assert(sendDuckSpies.showGasButtonGroup.calledOnce)
+ })
+ })
+
+ describe('resetCustomData()', () => {
+ it('should dispatch an action', () => {
+ mapDispatchToPropsObject.resetCustomData()
+ assert(dispatchSpy.calledOnce)
+ assert(gasDuckSpies.resetCustomData.calledOnce)
+ })
+ })
+
+ })
+
+ describe('mergeProps', () => {
+ let stateProps
+ let dispatchProps
+ let ownProps
+
+ beforeEach(() => {
+ stateProps = {
+ gasPriceButtonGroupProps: {
+ someGasPriceButtonGroupProp: 'foo',
+ anotherGasPriceButtonGroupProp: 'bar',
+ },
+ someOtherStateProp: 'baz',
+ }
+ dispatchProps = {
+ setGasPrice: sinon.spy(),
+ someOtherDispatchProp: sinon.spy(),
+ }
+ ownProps = { someOwnProp: 123 }
+ })
+
+ it('should return the expected props when isConfirm is true', () => {
+ const result = mergeProps(stateProps, dispatchProps, ownProps)
+
+ assert.equal(result.someOtherStateProp, 'baz')
+ assert.equal(result.gasPriceButtonGroupProps.someGasPriceButtonGroupProp, 'foo')
+ assert.equal(result.gasPriceButtonGroupProps.anotherGasPriceButtonGroupProp, 'bar')
+ assert.equal(result.someOwnProp, 123)
+
+ assert.equal(dispatchProps.setGasPrice.callCount, 0)
+ result.gasPriceButtonGroupProps.handleGasPriceSelection()
+ assert.equal(dispatchProps.setGasPrice.callCount, 1)
+
+ assert.equal(dispatchProps.someOtherDispatchProp.callCount, 0)
+ result.someOtherDispatchProp()
+ assert.equal(dispatchProps.someOtherDispatchProp.callCount, 1)
+ })
+ })
+
+})
diff --git a/ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js b/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
index d46dd9d8b..bd3c9a257 100644
--- a/ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
+++ b/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
@@ -2,6 +2,7 @@ import assert from 'assert'
import {
gasFeeIsInError,
getGasLoadingError,
+ getGasButtonGroupShown,
} from '../send-gas-row.selectors.js'
describe('send-gas-row selectors', () => {
@@ -46,4 +47,16 @@ describe('send-gas-row selectors', () => {
})
})
+ describe('getGasButtonGroupShown()', () => {
+ it('should return send.gasButtonGroupShown', () => {
+ const state = {
+ send: {
+ gasButtonGroupShown: 'foobar',
+ },
+ }
+
+ assert.equal(getGasButtonGroupShown(state), 'foobar')
+ })
+ })
+
})
diff --git a/ui/app/components/send/send-content/send-hex-data-row/index.js b/ui/app/components/app/send/send-content/send-hex-data-row/index.js
index 08c341067..08c341067 100644
--- a/ui/app/components/send/send-content/send-hex-data-row/index.js
+++ b/ui/app/components/app/send/send-content/send-hex-data-row/index.js
diff --git a/ui/app/components/send/send-content/send-hex-data-row/send-hex-data-row.component.js b/ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.component.js
index 62a74a77b..62a74a77b 100644
--- a/ui/app/components/send/send-content/send-hex-data-row/send-hex-data-row.component.js
+++ b/ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.component.js
diff --git a/ui/app/components/send/send-content/send-hex-data-row/send-hex-data-row.container.js b/ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.container.js
index df554ca5f..76c929d08 100644
--- a/ui/app/components/send/send-content/send-hex-data-row/send-hex-data-row.container.js
+++ b/ui/app/components/app/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 '../../../../actions'
+} from '../../../../../store/actions'
import SendHexDataRow from './send-hex-data-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendHexDataRow)
diff --git a/ui/app/components/send/send-content/send-row-wrapper/index.js b/ui/app/components/app/send/send-content/send-row-wrapper/index.js
index d17545dcc..d17545dcc 100644
--- a/ui/app/components/send/send-content/send-row-wrapper/index.js
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/index.js
diff --git a/ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/index.js b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/index.js
index c00617f83..c00617f83 100644
--- a/ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/index.js
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/index.js
diff --git a/ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md
diff --git a/ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
index 61bc7bab7..61bc7bab7 100644
--- a/ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
diff --git a/ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js
index 59622047f..59622047f 100644
--- a/ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js
diff --git a/ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss
diff --git a/ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js b/ui/app/components/app/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/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js
diff --git a/ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js
index eecff165d..eecff165d 100644
--- a/ui/app/components/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/index.js b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/index.js
new file mode 100644
index 000000000..fd4d19ef7
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/index.js
@@ -0,0 +1 @@
+export { default } from './send-row-warning-message.container'
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/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js
new file mode 100644
index 000000000..f1caa8f99
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js
@@ -0,0 +1,27 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+
+export default class SendRowWarningMessage extends Component {
+
+ static propTypes = {
+ warnings: PropTypes.object,
+ warningType: PropTypes.string,
+ };
+
+ static contextTypes = {
+ t: PropTypes.func,
+ };
+
+ render () {
+ const { warnings, warningType } = this.props
+
+ const warningMessage = warningType in warnings && warnings[warningType]
+
+ return (
+ warningMessage
+ ? <div className="send-v2__warning">{this.context.t(warningMessage)}</div>
+ : null
+ )
+ }
+
+}
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/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js
new file mode 100644
index 000000000..7df14fd96
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import { getSendWarnings } from '../../../send.selectors'
+import SendRowWarningMessage from './send-row-warning-message.component'
+
+export default connect(mapStateToProps)(SendRowWarningMessage)
+
+function mapStateToProps (state, ownProps) {
+ return {
+ warnings: getSendWarnings(state),
+ warningType: ownProps.warningType,
+ }
+}
diff --git a/ui/app/components/page-container/tests/page-container.component.test.js b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/page-container/tests/page-container.component.test.js
+++ b/ui/app/components/app/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/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js
new file mode 100644
index 000000000..bd803d833
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js
@@ -0,0 +1,28 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import SendRowWarningMessage from '../send-row-warning-message.component.js'
+
+describe('SendRowWarningMessage Component', function () {
+ let wrapper
+
+ beforeEach(() => {
+ wrapper = shallow(<SendRowWarningMessage
+ warnings={{ warning1: 'abc', warning2: 'def' }}
+ warningType={'warning3'}
+ />, { context: { t: str => str + '_t' } })
+ })
+
+ describe('render', () => {
+ it('should render null if the passed warnings do not contain a warning of warningType', () => {
+ assert.equal(wrapper.find('.send-v2__warning').length, 0)
+ assert.equal(wrapper.html(), null)
+ })
+
+ it('should render a warning message if the passed warnings contain a warning of warningType', () => {
+ wrapper.setProps({ warnings: { warning1: 'abc', warning2: 'def', warning3: 'xyz' } })
+ assert.equal(wrapper.find('.send-v2__warning').length, 1)
+ assert.equal(wrapper.find('.send-v2__warning').text(), 'xyz_t')
+ })
+ })
+})
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/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js
new file mode 100644
index 000000000..225bf056c
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js
@@ -0,0 +1,28 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+
+let mapStateToProps
+
+proxyquire('../send-row-warning-message.container.js', {
+ 'react-redux': {
+ connect: (ms, md) => {
+ mapStateToProps = ms
+ return () => ({})
+ },
+ },
+ '../../../send.selectors': { getSendWarnings: (s) => `mockWarnings:${s}` },
+})
+
+describe('send-row-warning-message container', () => {
+
+ describe('mapStateToProps()', () => {
+
+ it('should map the correct properties to props', () => {
+ assert.deepEqual(mapStateToProps('mockState', { warningType: 'someType' }), {
+ warnings: 'mockWarnings:mockState',
+ warningType: 'someType' })
+ })
+
+ })
+
+})
diff --git a/ui/app/components/send/send-content/send-row-wrapper/send-row-wrapper-README.md b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper-README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/send-content/send-row-wrapper/send-row-wrapper-README.md
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper-README.md
diff --git a/ui/app/components/send/send-content/send-row-wrapper/send-row-wrapper.component.js b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.component.js
index b7528a15f..94309bd96 100644
--- a/ui/app/components/send/send-content/send-row-wrapper/send-row-wrapper.component.js
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.component.js
@@ -1,6 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import SendRowErrorMessage from './send-row-error-message/'
+import SendRowErrorMessage from './send-row-error-message'
+import SendRowWarningMessage from './send-row-warning-message'
export default class SendRowWrapper extends Component {
@@ -9,6 +10,8 @@ export default class SendRowWrapper extends Component {
errorType: PropTypes.string,
label: PropTypes.string,
showError: PropTypes.bool,
+ showWarning: PropTypes.bool,
+ warningType: PropTypes.string,
};
static contextTypes = {
@@ -21,20 +24,22 @@ export default class SendRowWrapper extends Component {
errorType = '',
label,
showError = false,
+ showWarning = false,
+ warningType = '',
} = this.props
-
const formField = Array.isArray(children) ? children[1] || children[0] : children
const customLabelContent = children.length > 1 ? children[0] : null
return (
<div className="send-v2__form-row">
<div className="send-v2__form-label">
- {label}
- {showError && <SendRowErrorMessage errorType={errorType}/>}
- {customLabelContent}
+ {label}
+ {showError && <SendRowErrorMessage errorType={errorType}/>}
+ {!showError && showWarning && <SendRowWarningMessage warningType={warningType} />}
+ {customLabelContent}
</div>
<div className="send-v2__form-field">
- {formField}
+ {formField}
</div>
</div>
)
diff --git a/ui/app/components/send/send-content/send-row-wrapper/send-row-wrapper.scss b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/send-content/send-row-wrapper/send-row-wrapper.scss
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.scss
diff --git a/ui/app/components/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js b/ui/app/components/app/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js
index 30280e1d0..30280e1d0 100644
--- a/ui/app/components/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js
+++ b/ui/app/components/app/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js
diff --git a/ui/app/components/send/send-content/send-to-row/index.js b/ui/app/components/app/send/send-content/send-to-row/index.js
index 121f15148..121f15148 100644
--- a/ui/app/components/send/send-content/send-to-row/index.js
+++ b/ui/app/components/app/send/send-content/send-to-row/index.js
diff --git a/ui/app/components/send/send-content/send-to-row/send-to-row-README.md b/ui/app/components/app/send/send-content/send-to-row/send-to-row-README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/send-content/send-to-row/send-to-row-README.md
+++ b/ui/app/components/app/send/send-content/send-to-row/send-to-row-README.md
diff --git a/ui/app/components/send/send-content/send-to-row/send-to-row.component.js b/ui/app/components/app/send/send-content/send-to-row/send-to-row.component.js
index 17c75c817..e8a55cb2a 100644
--- a/ui/app/components/send/send-content/send-to-row/send-to-row.component.js
+++ b/ui/app/components/app/send/send-content/send-to-row/send-to-row.component.js
@@ -1,8 +1,8 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import SendRowWrapper from '../send-row-wrapper/'
+import SendRowWrapper from '../send-row-wrapper'
import EnsInput from '../../../ens-input'
-import { getToErrorObject } from './send-to-row.utils.js'
+import { getToErrorObject, getToWarningObject } from './send-to-row.utils.js'
export default class SendToRow extends Component {
@@ -10,26 +10,33 @@ export default class SendToRow extends Component {
closeToDropdown: PropTypes.func,
hasHexData: PropTypes.bool.isRequired,
inError: PropTypes.bool,
+ inWarning: PropTypes.bool,
network: PropTypes.string,
openToDropdown: PropTypes.func,
+ selectedToken: PropTypes.object,
to: PropTypes.string,
toAccounts: PropTypes.array,
toDropdownOpen: PropTypes.bool,
+ tokens: PropTypes.array,
updateGas: PropTypes.func,
updateSendTo: PropTypes.func,
updateSendToError: PropTypes.func,
+ updateSendToWarning: PropTypes.func,
scanQrCode: PropTypes.func,
- };
+ }
static contextTypes = {
t: PropTypes.func,
- };
+ metricsEvent: PropTypes.func,
+ }
- handleToChange (to, nickname = '', toError) {
- const { hasHexData, updateSendTo, updateSendToError, updateGas } = this.props
- const toErrorObject = getToErrorObject(to, toError, hasHexData)
+ handleToChange (to, nickname = '', toError, toWarning, network) {
+ const { hasHexData, updateSendTo, updateSendToError, updateGas, tokens, selectedToken, updateSendToWarning } = this.props
+ const toErrorObject = getToErrorObject(to, toError, hasHexData, tokens, selectedToken, network)
+ const toWarningObject = getToWarningObject(to, toWarning, tokens, selectedToken)
updateSendTo(to, nickname)
updateSendToError(toErrorObject)
+ updateSendToWarning(toWarningObject)
if (toErrorObject.to === null) {
updateGas({ to })
}
@@ -39,6 +46,7 @@ export default class SendToRow extends Component {
const {
closeToDropdown,
inError,
+ inWarning,
network,
openToDropdown,
to,
@@ -51,16 +59,27 @@ export default class SendToRow extends Component {
errorType={'to'}
label={`${this.context.t('to')}: `}
showError={inError}
- >
+ showWarning={inWarning}
+ warningType={'to'}
+ >
<EnsInput
- scanQrCode={_ => this.props.scanQrCode()}
+ scanQrCode={_ => {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Transactions',
+ action: 'Edit Screen',
+ name: 'Used QR scanner',
+ },
+ })
+ this.props.scanQrCode()
+ }}
accounts={toAccounts}
closeDropdown={() => closeToDropdown()}
dropdownOpen={toDropdownOpen}
inError={inError}
name={'address'}
network={network}
- onChange={({ toAddress, nickname, toError }) => this.handleToChange(toAddress, nickname, toError)}
+ onChange={({ toAddress, nickname, toError, toWarning }) => this.handleToChange(toAddress, nickname, toError, toWarning, this.props.network)}
openDropdown={() => openToDropdown()}
placeholder={this.context.t('recipientAddress')}
to={to}
diff --git a/ui/app/components/send/send-content/send-to-row/send-to-row.container.js b/ui/app/components/app/send/send-content/send-to-row/send-to-row.container.js
index 3ee188bad..30865d295 100644
--- a/ui/app/components/send/send-content/send-to-row/send-to-row.container.js
+++ b/ui/app/components/app/send/send-content/send-to-row/send-to-row.container.js
@@ -1,22 +1,26 @@
import { connect } from 'react-redux'
import {
getCurrentNetwork,
+ getSelectedToken,
getSendTo,
getSendToAccounts,
getSendHexData,
} from '../../send.selectors.js'
import {
getToDropdownOpen,
+ getTokens,
sendToIsInError,
+ sendToIsInWarning,
} from './send-to-row.selectors.js'
import {
updateSendTo,
-} from '../../../../actions'
+} from '../../../../../store/actions'
import {
updateSendErrors,
+ updateSendWarnings,
openToDropdown,
closeToDropdown,
-} from '../../../../ducks/send.duck'
+} from '../../../../../ducks/send/send.duck'
import SendToRow from './send-to-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendToRow)
@@ -25,10 +29,13 @@ function mapStateToProps (state) {
return {
hasHexData: Boolean(getSendHexData(state)),
inError: sendToIsInError(state),
+ inWarning: sendToIsInWarning(state),
network: getCurrentNetwork(state),
+ selectedToken: getSelectedToken(state),
to: getSendTo(state),
toAccounts: getSendToAccounts(state),
toDropdownOpen: getToDropdownOpen(state),
+ tokens: getTokens(state),
}
}
@@ -40,5 +47,8 @@ function mapDispatchToProps (dispatch) {
updateSendToError: (toErrorObject) => {
dispatch(updateSendErrors(toErrorObject))
},
+ updateSendToWarning: (toWarningObject) => {
+ dispatch(updateSendWarnings(toWarningObject))
+ },
}
}
diff --git a/ui/app/components/send/send-content/send-to-row/send-to-row.selectors.js b/ui/app/components/app/send/send-content/send-to-row/send-to-row.selectors.js
index 8919014be..a6160d335 100644
--- a/ui/app/components/send/send-content/send-to-row/send-to-row.selectors.js
+++ b/ui/app/components/app/send/send-content/send-to-row/send-to-row.selectors.js
@@ -1,6 +1,8 @@
const selectors = {
getToDropdownOpen,
+ getTokens,
sendToIsInError,
+ sendToIsInWarning,
}
module.exports = selectors
@@ -12,3 +14,11 @@ function getToDropdownOpen (state) {
function sendToIsInError (state) {
return Boolean(state.send.errors.to)
}
+
+function sendToIsInWarning (state) {
+ return Boolean(state.send.warnings.to)
+}
+
+function getTokens (state) {
+ return state.metamask.tokens
+}
diff --git a/ui/app/components/app/send/send-content/send-to-row/send-to-row.utils.js b/ui/app/components/app/send/send-content/send-to-row/send-to-row.utils.js
new file mode 100644
index 000000000..60e75d34c
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-to-row/send-to-row.utils.js
@@ -0,0 +1,36 @@
+const {
+ REQUIRED_ERROR,
+ INVALID_RECIPIENT_ADDRESS_ERROR,
+ 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 ethUtil = require('ethereumjs-util')
+const contractMap = require('eth-contract-metadata')
+
+function getToErrorObject (to, toError = null, hasHexData = false, tokens = [], selectedToken = null, 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 }
+}
+
+function getToWarningObject (to, toWarning = null, tokens = [], selectedToken = null) {
+ if (selectedToken && (ethUtil.toChecksumAddress(to) in contractMap || checkExistingAddresses(to, tokens))) {
+ toWarning = KNOWN_RECIPIENT_ADDRESS_ERROR
+ }
+ return { to: toWarning }
+}
+
+module.exports = {
+ getToErrorObject,
+ getToWarningObject,
+}
diff --git a/ui/app/components/send/send-content/send-to-row/tests/send-to-row-component.test.js b/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-component.test.js
index 591229deb..d4d054057 100644
--- a/ui/app/components/send/send-content/send-to-row/tests/send-to-row-component.test.js
+++ b/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-component.test.js
@@ -9,6 +9,9 @@ const SendToRow = proxyquire('../send-to-row.component.js', {
getToErrorObject: (to, toError) => ({
to: to === false ? null : `mockToErrorObject:${to}${toError}`,
}),
+ getToWarningObject: (to, toWarning) => ({
+ to: to === false ? null : `mockToWarningObject:${to}${toWarning}`,
+ }),
},
}).default
@@ -21,6 +24,7 @@ const propsMethodSpies = {
updateGas: sinon.spy(),
updateSendTo: sinon.spy(),
updateSendToError: sinon.spy(),
+ updateSendToWarning: sinon.spy(),
}
sinon.spy(SendToRow.prototype, 'handleToChange')
@@ -33,6 +37,7 @@ describe('SendToRow Component', function () {
wrapper = shallow(<SendToRow
closeToDropdown={propsMethodSpies.closeToDropdown}
inError={false}
+ inWarning={false}
network={'mockNetwork'}
openToDropdown={propsMethodSpies.openToDropdown}
to={'mockTo'}
@@ -41,6 +46,7 @@ describe('SendToRow Component', function () {
updateGas={propsMethodSpies.updateGas}
updateSendTo={propsMethodSpies.updateSendTo}
updateSendToError={propsMethodSpies.updateSendToError}
+ updateSendToWarning={propsMethodSpies.updateSendToWarning}
/>, { context: { t: str => str + '_t' } })
instance = wrapper.instance()
})
@@ -50,6 +56,7 @@ describe('SendToRow Component', function () {
propsMethodSpies.openToDropdown.resetHistory()
propsMethodSpies.updateSendTo.resetHistory()
propsMethodSpies.updateSendToError.resetHistory()
+ propsMethodSpies.updateSendToWarning.resetHistory()
SendToRow.prototype.handleToChange.resetHistory()
})
@@ -75,6 +82,16 @@ describe('SendToRow Component', function () {
)
})
+ it('should call updateSendToWarning', () => {
+ assert.equal(propsMethodSpies.updateSendToWarning.callCount, 0)
+ instance.handleToChange('mockTo2', '', '', 'mockToWarning')
+ assert.equal(propsMethodSpies.updateSendToWarning.callCount, 1)
+ assert.deepEqual(
+ propsMethodSpies.updateSendToWarning.getCall(0).args,
+ [{ to: 'mockToWarningObject:mockTo2mockToWarning' }]
+ )
+ })
+
it('should not call updateGas if there is a to error', () => {
assert.equal(propsMethodSpies.updateGas.callCount, 0)
instance.handleToChange('mockTo2')
@@ -138,11 +155,11 @@ describe('SendToRow Component', function () {
openDropdown()
assert.equal(propsMethodSpies.openToDropdown.callCount, 1)
assert.equal(SendToRow.prototype.handleToChange.callCount, 0)
- onChange({ toAddress: 'mockNewTo', nickname: 'mockNewNickname', toError: 'mockToError' })
+ onChange({ toAddress: 'mockNewTo', nickname: 'mockNewNickname', toError: 'mockToError', toWarning: 'mockToWarning' })
assert.equal(SendToRow.prototype.handleToChange.callCount, 1)
assert.deepEqual(
SendToRow.prototype.handleToChange.getCall(0).args,
- ['mockNewTo', 'mockNewNickname', 'mockToError']
+ ['mockNewTo', 'mockNewNickname', 'mockToError', 'mockToWarning', 'mockNetwork' ]
)
})
})
diff --git a/ui/app/components/send/send-content/send-to-row/tests/send-to-row-container.test.js b/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-container.test.js
index dfce7652f..94b4f1024 100644
--- a/ui/app/components/send/send-content/send-to-row/tests/send-to-row-container.test.js
+++ b/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-container.test.js
@@ -12,6 +12,7 @@ const duckActionSpies = {
closeToDropdown: sinon.spy(),
openToDropdown: sinon.spy(),
updateSendErrors: sinon.spy(),
+ updateSendWarnings: sinon.spy(),
}
proxyquire('../send-to-row.container.js', {
@@ -24,6 +25,7 @@ proxyquire('../send-to-row.container.js', {
},
'../../send.selectors.js': {
getCurrentNetwork: (s) => `mockNetwork:${s}`,
+ getSelectedToken: (s) => `mockSelectedToken:${s}`,
getSendHexData: (s) => s,
getSendTo: (s) => `mockTo:${s}`,
getSendToAccounts: (s) => `mockToAccounts:${s}`,
@@ -31,9 +33,11 @@ proxyquire('../send-to-row.container.js', {
'./send-to-row.selectors.js': {
getToDropdownOpen: (s) => `mockToDropdownOpen:${s}`,
sendToIsInError: (s) => `mockInError:${s}`,
+ sendToIsInWarning: (s) => `mockInWarning:${s}`,
+ getTokens: (s) => `mockTokens:${s}`,
},
- '../../../../actions': actionSpies,
- '../../../../ducks/send.duck': duckActionSpies,
+ '../../../../../store/actions': actionSpies,
+ '../../../../../ducks/send/send.duck': duckActionSpies,
})
describe('send-to-row container', () => {
@@ -44,10 +48,13 @@ describe('send-to-row container', () => {
assert.deepEqual(mapStateToProps('mockState'), {
hasHexData: true,
inError: 'mockInError:mockState',
+ inWarning: 'mockInWarning:mockState',
network: 'mockNetwork:mockState',
+ selectedToken: 'mockSelectedToken:mockState',
to: 'mockTo:mockState',
toAccounts: 'mockToAccounts:mockState',
toDropdownOpen: 'mockToDropdownOpen:mockState',
+ tokens: 'mockTokens:mockState',
})
})
@@ -110,6 +117,18 @@ describe('send-to-row container', () => {
})
})
+ describe('updateSendToWarning()', () => {
+ it('should dispatch an action', () => {
+ mapDispatchToPropsObject.updateSendToWarning('mockToWarningObject')
+ assert(dispatchSpy.calledOnce)
+ assert(duckActionSpies.updateSendWarnings.calledOnce)
+ assert.equal(
+ duckActionSpies.updateSendWarnings.getCall(0).args[0],
+ 'mockToWarningObject'
+ )
+ })
+ })
+
})
})
diff --git a/ui/app/components/send/send-content/send-to-row/tests/send-to-row-selectors.test.js b/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-selectors.test.js
index 122ad3265..0fa342d1e 100644
--- a/ui/app/components/send/send-content/send-to-row/tests/send-to-row-selectors.test.js
+++ b/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-selectors.test.js
@@ -1,6 +1,7 @@
import assert from 'assert'
import {
getToDropdownOpen,
+ getTokens,
sendToIsInError,
} from '../send-to-row.selectors.js'
@@ -44,4 +45,15 @@ describe('send-to-row selectors', () => {
})
})
+ describe('getTokens()', () => {
+ it('should return empty array if no tokens in state', () => {
+ const state = {
+ metamask: {
+ tokens: [],
+ },
+ }
+
+ assert.deepStrictEqual(getTokens(state), [])
+ })
+ })
})
diff --git a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-utils.test.js b/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-utils.test.js
new file mode 100644
index 000000000..95882d640
--- /dev/null
+++ b/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-utils.test.js
@@ -0,0 +1,107 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+import sinon from 'sinon'
+
+import {
+ REQUIRED_ERROR,
+ INVALID_RECIPIENT_ADDRESS_ERROR,
+ KNOWN_RECIPIENT_ADDRESS_ERROR,
+} from '../../../send.constants'
+
+const stubs = {
+ isValidAddress: sinon.stub().callsFake(to => Boolean(to.match(/^[0xabcdef123456798]+$/))),
+}
+
+const toRowUtils = proxyquire('../send-to-row.utils.js', {
+ '../../../../../helpers/utils/util': {
+ isValidAddress: stubs.isValidAddress,
+ },
+})
+const {
+ getToErrorObject,
+ getToWarningObject,
+} = toRowUtils
+
+describe('send-to-row utils', () => {
+
+ describe('getToErrorObject()', () => {
+ it('should return a required error if to is falsy', () => {
+ assert.deepEqual(getToErrorObject(null), {
+ to: REQUIRED_ERROR,
+ })
+ })
+
+ it('should return null if to is falsy and hexData is truthy', () => {
+ assert.deepEqual(getToErrorObject(null, undefined, true), {
+ to: null,
+ })
+ })
+
+ it('should return an invalid recipient error if to is truthy but invalid', () => {
+ assert.deepEqual(getToErrorObject('mockInvalidTo'), {
+ to: INVALID_RECIPIENT_ADDRESS_ERROR,
+ })
+ })
+
+ it('should return null if to is truthy and valid', () => {
+ assert.deepEqual(getToErrorObject('0xabc123'), {
+ to: null,
+ })
+ })
+
+ it('should return the passed error if to is truthy but invalid if to is truthy and valid', () => {
+ assert.deepEqual(getToErrorObject('invalid #$ 345878', 'someExplicitError'), {
+ to: 'someExplicitError',
+ })
+ })
+
+ it('should return a known address recipient if to is truthy but part of state tokens', () => {
+ assert.deepEqual(getToErrorObject('0xabc123', undefined, false, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
+ to: KNOWN_RECIPIENT_ADDRESS_ERROR,
+ })
+ })
+
+ it('should null if to is truthy part of tokens but selectedToken falsy', () => {
+ assert.deepEqual(getToErrorObject('0xabc123', undefined, false, [{'address': '0xabc123'}]), {
+ to: null,
+ })
+ })
+
+ it('should return a known address recipient if to is truthy but part of contract metadata', () => {
+ assert.deepEqual(getToErrorObject('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', undefined, false, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
+ to: KNOWN_RECIPIENT_ADDRESS_ERROR,
+ })
+ })
+ 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,
+ })
+ })
+ })
+
+ describe('getToWarningObject()', () => {
+ it('should return a known address recipient if to is truthy but part of state tokens', () => {
+ assert.deepEqual(getToWarningObject('0xabc123', undefined, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
+ to: KNOWN_RECIPIENT_ADDRESS_ERROR,
+ })
+ })
+
+ it('should null if to is truthy part of tokens but selectedToken falsy', () => {
+ assert.deepEqual(getToWarningObject('0xabc123', undefined, [{'address': '0xabc123'}]), {
+ to: null,
+ })
+ })
+
+ it('should return a known address recipient if to is truthy but part of contract metadata', () => {
+ assert.deepEqual(getToWarningObject('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', undefined, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
+ to: KNOWN_RECIPIENT_ADDRESS_ERROR,
+ })
+ })
+ it('should null if to is truthy part of contract metadata but selectedToken falsy', () => {
+ assert.deepEqual(getToWarningObject('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', undefined, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
+ to: KNOWN_RECIPIENT_ADDRESS_ERROR,
+ })
+ })
+ })
+
+})
diff --git a/ui/app/components/send/send-content/tests/send-content-component.test.js b/ui/app/components/app/send/send-content/tests/send-content-component.test.js
index c5a11c8bb..7d102c930 100644
--- a/ui/app/components/send/send-content/tests/send-content-component.test.js
+++ b/ui/app/components/app/send/send-content/tests/send-content-component.test.js
@@ -3,7 +3,7 @@ import assert from 'assert'
import { shallow } from 'enzyme'
import SendContent from '../send-content.component.js'
-import PageContainerContent from '../../../page-container/page-container-content.component'
+import PageContainerContent from '../../../../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'
diff --git a/ui/app/components/send/send-footer/README.md b/ui/app/components/app/send/send-footer/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/send-footer/README.md
+++ b/ui/app/components/app/send/send-footer/README.md
diff --git a/ui/app/components/send/send-footer/index.js b/ui/app/components/app/send/send-footer/index.js
index 58e91d622..58e91d622 100644
--- a/ui/app/components/send/send-footer/index.js
+++ b/ui/app/components/app/send/send-footer/index.js
diff --git a/ui/app/components/send/send-footer/send-footer.component.js b/ui/app/components/app/send/send-footer/send-footer.component.js
index 230bf450f..cc891a9b3 100644
--- a/ui/app/components/send/send-footer/send-footer.component.js
+++ b/ui/app/components/app/send/send-footer/send-footer.component.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import PageContainerFooter from '../../page-container/page-container-footer'
-import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../routes'
+import PageContainerFooter from '../../../ui/page-container/page-container-footer'
+import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../../helpers/constants/routes'
export default class SendFooter extends Component {
@@ -26,10 +26,12 @@ export default class SendFooter extends Component {
tokenBalance: PropTypes.string,
unapprovedTxs: PropTypes.object,
update: PropTypes.func,
- };
+ sendErrors: PropTypes.object,
+ }
static contextTypes = {
t: PropTypes.func,
+ metricsEvent: PropTypes.func,
};
onCancel () {
@@ -56,6 +58,7 @@ export default class SendFooter extends Component {
toAccounts,
history,
} = this.props
+ const { metricsEvent } = this.context
// Should not be needed because submit should be disabled if there are errors.
// const noErrors = !amountError && toError === null
@@ -66,7 +69,6 @@ export default class SendFooter extends Component {
// TODO: add nickname functionality
addToAddressBookIfNew(to, toAccounts)
-
const promise = editingTransactionId
? update({
amount,
@@ -82,13 +84,44 @@ export default class SendFooter extends Component {
: sign({ data, selectedToken, to, amount, from, gas, gasPrice })
Promise.resolve(promise)
- .then(() => history.push(CONFIRM_TRANSACTION_ROUTE))
+ .then(() => {
+ metricsEvent({
+ eventOpts: {
+ category: 'Transactions',
+ action: 'Edit Screen',
+ name: 'Complete',
+ },
+ })
+ history.push(CONFIRM_TRANSACTION_ROUTE)
+ })
}
formShouldBeDisabled () {
const { data, inError, selectedToken, tokenBalance, gasTotal, to } = this.props
const missingTokenBalance = selectedToken && !tokenBalance
- return inError || !gasTotal || missingTokenBalance || !(data || to)
+ const shouldBeDisabled = inError || !gasTotal || missingTokenBalance || !(data || to)
+ return shouldBeDisabled
+ }
+
+ componentDidUpdate (prevProps) {
+ const { inError, sendErrors } = this.props
+ const { metricsEvent } = this.context
+ if (!prevProps.inError && inError) {
+ const errorField = Object.keys(sendErrors).find(key => sendErrors[key])
+ const errorMessage = sendErrors[errorField]
+
+ metricsEvent({
+ eventOpts: {
+ category: 'Transactions',
+ action: 'Edit Screen',
+ name: 'Error',
+ },
+ customVariables: {
+ errorField,
+ errorMessage,
+ },
+ })
+ }
}
render () {
diff --git a/ui/app/components/send/send-footer/send-footer.container.js b/ui/app/components/app/send/send-footer/send-footer.container.js
index 60de4d030..502159a81 100644
--- a/ui/app/components/send/send-footer/send-footer.container.js
+++ b/ui/app/components/app/send/send-footer/send-footer.container.js
@@ -6,7 +6,7 @@ import {
signTokenTx,
signTx,
updateTransaction,
-} from '../../../actions'
+} from '../../../../store/actions'
import SendFooter from './send-footer.component'
import {
getGasLimit,
@@ -21,6 +21,7 @@ import {
getSendHexData,
getTokenBalance,
getUnapprovedTxs,
+ getSendErrors,
} from '../send.selectors'
import {
isSendFormInError,
@@ -48,6 +49,7 @@ function mapStateToProps (state) {
toAccounts: getSendToAccounts(state),
tokenBalance: getTokenBalance(state),
unapprovedTxs: getUnapprovedTxs(state),
+ sendErrors: getSendErrors(state),
}
}
diff --git a/ui/app/components/send/send-footer/send-footer.scss b/ui/app/components/app/send/send-footer/send-footer.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/send-footer/send-footer.scss
+++ b/ui/app/components/app/send/send-footer/send-footer.scss
diff --git a/ui/app/components/send/send-footer/send-footer.selectors.js b/ui/app/components/app/send/send-footer/send-footer.selectors.js
index e20addfdc..e20addfdc 100644
--- a/ui/app/components/send/send-footer/send-footer.selectors.js
+++ b/ui/app/components/app/send/send-footer/send-footer.selectors.js
diff --git a/ui/app/components/send/send-footer/send-footer.utils.js b/ui/app/components/app/send/send-footer/send-footer.utils.js
index f82ff1e9b..f82ff1e9b 100644
--- a/ui/app/components/send/send-footer/send-footer.utils.js
+++ b/ui/app/components/app/send/send-footer/send-footer.utils.js
diff --git a/ui/app/components/send/send-footer/tests/send-footer-component.test.js b/ui/app/components/app/send/send-footer/tests/send-footer-component.test.js
index 65e4bb654..6683ca8c0 100644
--- a/ui/app/components/send/send-footer/tests/send-footer-component.test.js
+++ b/ui/app/components/app/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 '../../../../routes'
+import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../../../helpers/constants/routes'
import SendFooter from '../send-footer.component.js'
-import PageContainerFooter from '../../../page-container/page-container-footer'
+import PageContainerFooter from '../../../../ui/page-container/page-container-footer'
const propsMethodSpies = {
addToAddressBookIfNew: sinon.spy(),
@@ -45,7 +45,8 @@ describe('SendFooter Component', function () {
tokenBalance={'mockTokenBalance'}
unapprovedTxs={['mockTx']}
update={propsMethodSpies.update}
- />, { context: { t: str => str } })
+ sendErrors={{}}
+ />, { context: { t: str => str, metricsEvent: () => ({}) } })
})
afterEach(() => {
@@ -201,7 +202,7 @@ describe('SendFooter Component', function () {
tokenBalance={'mockTokenBalance'}
unapprovedTxs={['mockTx']}
update={propsMethodSpies.update}
- />, { context: { t: str => str } })
+ />, { context: { t: str => str, metricsEvent: () => ({}) } })
})
afterEach(() => {
diff --git a/ui/app/components/send/send-footer/tests/send-footer-container.test.js b/ui/app/components/app/send/send-footer/tests/send-footer-container.test.js
index cf4c893ee..878b0aa19 100644
--- a/ui/app/components/send/send-footer/tests/send-footer-container.test.js
+++ b/ui/app/components/app/send/send-footer/tests/send-footer-container.test.js
@@ -14,7 +14,9 @@ const actionSpies = {
}
const utilsStubs = {
addressIsNew: sinon.stub().returns(true),
- constructTxParams: sinon.stub().returns('mockConstructedTxParams'),
+ constructTxParams: sinon.stub().returns({
+ value: 'mockAmount',
+ }),
constructUpdatedTx: sinon.stub().returns('mockConstructedUpdatedTxParams'),
}
@@ -26,7 +28,7 @@ proxyquire('../send-footer.container.js', {
return () => ({})
},
},
- '../../../actions': actionSpies,
+ '../../../../store/actions': actionSpies,
'../send.selectors': {
getGasLimit: (s) => `mockGasLimit:${s}`,
getGasPrice: (s) => `mockGasPrice:${s}`,
@@ -40,6 +42,7 @@ proxyquire('../send-footer.container.js', {
getTokenBalance: (s) => `mockTokenBalance:${s}`,
getSendHexData: (s) => `mockHexData:${s}`,
getUnapprovedTxs: (s) => `mockUnapprovedTxs:${s}`,
+ getSendErrors: (s) => `mockSendErrors:${s}`,
},
'./send-footer.selectors': { isSendFormInError: (s) => `mockInError:${s}` },
'./send-footer.utils': utilsStubs,
@@ -64,6 +67,7 @@ describe('send-footer container', () => {
toAccounts: 'mockToAccounts:mockState',
tokenBalance: 'mockTokenBalance:mockState',
unapprovedTxs: 'mockUnapprovedTxs:mockState',
+ sendErrors: 'mockSendErrors:mockState',
})
})
@@ -115,7 +119,7 @@ describe('send-footer container', () => {
)
assert.deepEqual(
actionSpies.signTokenTx.getCall(0).args,
- [ '0xabc', 'mockTo', 'mockAmount', 'mockConstructedTxParams' ]
+ [ '0xabc', 'mockTo', 'mockAmount', { value: 'mockAmount' } ]
)
})
@@ -143,7 +147,7 @@ describe('send-footer container', () => {
)
assert.deepEqual(
actionSpies.signTx.getCall(0).args,
- [ 'mockConstructedTxParams' ]
+ [ { value: 'mockAmount' } ]
)
})
})
diff --git a/ui/app/components/send/send-footer/tests/send-footer-selectors.test.js b/ui/app/components/app/send/send-footer/tests/send-footer-selectors.test.js
index 8de032f57..8de032f57 100644
--- a/ui/app/components/send/send-footer/tests/send-footer-selectors.test.js
+++ b/ui/app/components/app/send/send-footer/tests/send-footer-selectors.test.js
diff --git a/ui/app/components/send/send-footer/tests/send-footer-utils.test.js b/ui/app/components/app/send/send-footer/tests/send-footer-utils.test.js
index 28ff0c891..28ff0c891 100644
--- a/ui/app/components/send/send-footer/tests/send-footer-utils.test.js
+++ b/ui/app/components/app/send/send-footer/tests/send-footer-utils.test.js
diff --git a/ui/app/components/send/send-header/README.md b/ui/app/components/app/send/send-header/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/send-header/README.md
+++ b/ui/app/components/app/send/send-header/README.md
diff --git a/ui/app/components/send/send-header/index.js b/ui/app/components/app/send/send-header/index.js
index 0b17f0b7d..0b17f0b7d 100644
--- a/ui/app/components/send/send-header/index.js
+++ b/ui/app/components/app/send/send-header/index.js
diff --git a/ui/app/components/send/send-header/send-header.component.js b/ui/app/components/app/send/send-header/send-header.component.js
index efc4bbf27..f216954ef 100644
--- a/ui/app/components/send/send-header/send-header.component.js
+++ b/ui/app/components/app/send/send-header/send-header.component.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import PageContainerHeader from '../../page-container/page-container-header'
-import { DEFAULT_ROUTE } from '../../../routes'
+import PageContainerHeader from '../../../ui/page-container/page-container-header'
+import { DEFAULT_ROUTE } from '../../../../helpers/constants/routes'
export default class SendHeader extends Component {
diff --git a/ui/app/components/send/send-header/send-header.container.js b/ui/app/components/app/send/send-header/send-header.container.js
index 4bcd0d1b6..ce53fba9a 100644
--- a/ui/app/components/send/send-header/send-header.container.js
+++ b/ui/app/components/app/send/send-header/send-header.container.js
@@ -1,5 +1,5 @@
import { connect } from 'react-redux'
-import { clearSend } from '../../../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/send/send-header/send-header.selectors.js b/ui/app/components/app/send/send-header/send-header.selectors.js
index d7c9d3766..d7c9d3766 100644
--- a/ui/app/components/send/send-header/send-header.selectors.js
+++ b/ui/app/components/app/send/send-header/send-header.selectors.js
diff --git a/ui/app/components/send/send-header/tests/send-header-component.test.js b/ui/app/components/app/send/send-header/tests/send-header-component.test.js
index 930bfa387..db2ee8967 100644
--- a/ui/app/components/send/send-header/tests/send-header-component.test.js
+++ b/ui/app/components/app/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 '../../../../routes'
+import { DEFAULT_ROUTE } from '../../../../../helpers/constants/routes'
import SendHeader from '../send-header.component.js'
-import PageContainerHeader from '../../../page-container/page-container-header'
+import PageContainerHeader from '../../../../ui/page-container/page-container-header'
const propsMethodSpies = {
clearSend: sinon.spy(),
diff --git a/ui/app/components/send/send-header/tests/send-header-container.test.js b/ui/app/components/app/send/send-header/tests/send-header-container.test.js
index 41a7e8a89..634c3424b 100644
--- a/ui/app/components/send/send-header/tests/send-header-container.test.js
+++ b/ui/app/components/app/send/send-header/tests/send-header-container.test.js
@@ -17,7 +17,7 @@ proxyquire('../send-header.container.js', {
return () => ({})
},
},
- '../../../actions': actionSpies,
+ '../../../../store/actions': actionSpies,
'./send-header.selectors': {
getTitleKey: (s) => `mockTitleKey:${s}`,
getSubtitleParams: (s) => `mockSubtitleParams:${s}`,
diff --git a/ui/app/components/send/send-header/tests/send-header-selectors.test.js b/ui/app/components/app/send/send-header/tests/send-header-selectors.test.js
index e0c6a3ab3..e0c6a3ab3 100644
--- a/ui/app/components/send/send-header/tests/send-header-selectors.test.js
+++ b/ui/app/components/app/send/send-header/tests/send-header-selectors.test.js
diff --git a/ui/app/components/send/send.component.js b/ui/app/components/app/send/send.component.js
index fb7beca16..a38b681b0 100644
--- a/ui/app/components/send/send.component.js
+++ b/ui/app/components/app/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,
@@ -8,9 +8,9 @@ import {
doesAmountErrorRequireUpdate,
} from './send.utils'
-import SendHeader from './send-header/'
-import SendContent from './send-content/'
-import SendFooter from './send-footer/'
+import SendHeader from './send-header'
+import SendContent from './send-content'
+import SendFooter from './send-footer'
export default class SendTransactionScreen extends PersistentForm {
@@ -35,17 +35,18 @@ export default class SendTransactionScreen extends PersistentForm {
selectedToken: PropTypes.object,
tokenBalance: PropTypes.string,
tokenContract: PropTypes.object,
+ fetchBasicGasEstimates: PropTypes.func,
updateAndSetGasTotal: PropTypes.func,
updateSendErrors: PropTypes.func,
updateSendTokenBalance: PropTypes.func,
scanQrCode: PropTypes.func,
qrCodeDetected: PropTypes.func,
qrCodeData: PropTypes.object,
- };
+ }
static contextTypes = {
t: PropTypes.func,
- };
+ }
componentWillReceiveProps (nextProps) {
if (nextProps.qrCodeData) {
@@ -73,10 +74,10 @@ export default class SendTransactionScreen extends PersistentForm {
selectedAddress,
selectedToken = {},
to: currentToAddress,
- updateAndSetGasTotal,
+ updateAndSetGasLimit,
} = this.props
- updateAndSetGasTotal({
+ updateAndSetGasLimit({
blockGasLimit,
editingTransactionId,
gasLimit,
@@ -138,14 +139,12 @@ export default class SendTransactionScreen extends PersistentForm {
})
const gasFeeErrorObject = selectedToken
? getGasFeeErrorObject({
- amount,
amountConversionRate,
balance,
conversionRate,
gasTotal,
primaryCurrency,
selectedToken,
- tokenBalance,
})
: { gasFee: null }
updateSendErrors(Object.assign(amountErrorObject, gasFeeErrorObject))
@@ -164,6 +163,13 @@ export default class SendTransactionScreen extends PersistentForm {
}
}
+ componentDidMount () {
+ this.props.fetchBasicGasEstimates()
+ .then(() => {
+ this.updateGas()
+ })
+ }
+
componentWillMount () {
const {
from: { address },
@@ -171,12 +177,12 @@ export default class SendTransactionScreen extends PersistentForm {
tokenContract,
updateSendTokenBalance,
} = this.props
+
updateSendTokenBalance({
selectedToken,
tokenContract,
address,
})
- this.updateGas()
// Show QR Scanner modal if ?scan=true
if (window.location.search === '?scan=true') {
diff --git a/ui/app/components/send/send.constants.js b/ui/app/components/app/send/send.constants.js
index 8acdf0641..36549038e 100644
--- a/ui/app/components/send/send.constants.js
+++ b/ui/app/components/app/send/send.constants.js
@@ -1,5 +1,5 @@
const ethUtil = require('ethereumjs-util')
-const { conversionUtil, multiplyCurrencies } = require('../../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)
@@ -26,7 +26,9 @@ const INSUFFICIENT_FUNDS_ERROR = 'insufficientFunds'
const INSUFFICIENT_TOKENS_ERROR = 'insufficientTokens'
const NEGATIVE_ETH_ERROR = 'negativeETH'
const INVALID_RECIPIENT_ADDRESS_ERROR = 'invalidAddressRecipient'
+const INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR = 'invalidAddressRecipientNotEthNetwork'
const REQUIRED_ERROR = 'required'
+const KNOWN_RECIPIENT_ADDRESS_ERROR = 'knownAddressRecipient'
const ONE_GWEI_IN_WEI_HEX = ethUtil.addHexPrefix(conversionUtil('0x1', {
fromDenomination: 'GWEI',
@@ -42,6 +44,8 @@ module.exports = {
INSUFFICIENT_FUNDS_ERROR,
INSUFFICIENT_TOKENS_ERROR,
INVALID_RECIPIENT_ADDRESS_ERROR,
+ KNOWN_RECIPIENT_ADDRESS_ERROR,
+ INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR,
MIN_GAS_LIMIT_DEC,
MIN_GAS_LIMIT_HEX,
MIN_GAS_PRICE_DEC,
diff --git a/ui/app/components/send/send.container.js b/ui/app/components/app/send/send.container.js
index 87056499f..e65463b93 100644
--- a/ui/app/components/send/send.container.js
+++ b/ui/app/components/app/send/send.container.js
@@ -31,18 +31,21 @@ import {
setGasTotal,
showQrScanner,
qrCodeDetected,
-} from '../../actions'
+} from '../../../store/actions'
import {
resetSendState,
updateSendErrors,
-} from '../../ducks/send.duck'
+} from '../../../ducks/send/send.duck'
+import {
+ fetchBasicGasEstimates,
+} from '../../../ducks/gas/gas.duck'
import {
calcGasTotal,
} from './send.utils.js'
import {
SEND_ROUTE,
-} from '../../routes'
+} from '../../../helpers/constants/routes'
module.exports = compose(
withRouter,
@@ -76,7 +79,7 @@ function mapStateToProps (state) {
function mapDispatchToProps (dispatch) {
return {
- updateAndSetGasTotal: ({
+ updateAndSetGasLimit: ({
blockGasLimit,
editingTransactionId,
gasLimit,
@@ -89,7 +92,7 @@ function mapDispatchToProps (dispatch) {
data,
}) => {
!editingTransactionId
- ? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, blockGasLimit, to, value, data }))
+ ? dispatch(updateGasData({ gasPrice, recentBlocks, selectedAddress, selectedToken, blockGasLimit, to, value, data }))
: dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice)))
},
updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => {
@@ -104,5 +107,6 @@ function mapDispatchToProps (dispatch) {
scanQrCode: () => dispatch(showQrScanner(SEND_ROUTE)),
qrCodeDetected: (data) => dispatch(qrCodeDetected(data)),
updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
+ fetchBasicGasEstimates: () => dispatch(fetchBasicGasEstimates()),
}
}
diff --git a/ui/app/components/send/send.scss b/ui/app/components/app/send/send.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/send.scss
+++ b/ui/app/components/app/send/send.scss
diff --git a/ui/app/components/send/send.selectors.js b/ui/app/components/app/send/send.selectors.js
index eb22a08b7..2ec677ad1 100644
--- a/ui/app/components/send/send.selectors.js
+++ b/ui/app/components/app/send/send.selectors.js
@@ -1,15 +1,21 @@
-const { valuesFor } = require('../../util')
+const { valuesFor } = require('../../../helpers/utils/util')
const abi = require('human-standard-token-abi')
const {
multiplyCurrencies,
-} = require('../../conversion-util')
+} = require('../../../helpers/utils/conversion-util')
+const {
+ getMetaMaskAccounts,
+} = require('../../../selectors/selectors')
const {
estimateGasPriceFromRecentBlocks,
+ calcGasTotal,
} = require('./send.utils')
+import {
+ getFastPriceEstimateInHexWEI,
+} from '../../../selectors/custom-gas'
const selectors = {
accountsWithSendEtherInfoSelector,
- // autoAddToBetaUI,
getAddressBook,
getAmountConversionRate,
getBlockGasLimit,
@@ -44,6 +50,7 @@ const selectors = {
getSendMaxModeState,
getSendTo,
getSendToAccounts,
+ getSendWarnings,
getTokenBalance,
getTokenExchangeRate,
getUnapprovedTxs,
@@ -54,10 +61,8 @@ const selectors = {
module.exports = selectors
function accountsWithSendEtherInfoSelector (state) {
- const {
- accounts,
- identities,
- } = state.metamask
+ const accounts = getMetaMaskAccounts(state)
+ const { identities } = state.metamask
const accountsWithSendEtherInfo = Object.entries(accounts).map(([key, account]) => {
return Object.assign({}, account, identities[key])
@@ -66,23 +71,6 @@ function accountsWithSendEtherInfoSelector (state) {
return accountsWithSendEtherInfo
}
-// function autoAddToBetaUI (state) {
-// const autoAddTransactionThreshold = 12
-// const autoAddAccountsThreshold = 2
-// const autoAddTokensThreshold = 1
-
-// const numberOfTransactions = state.metamask.selectedAddressTxList.length
-// const numberOfAccounts = Object.keys(state.metamask.accounts).length
-// const numberOfTokensAdded = state.metamask.tokens.length
-
-// const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) &&
-// (numberOfAccounts > autoAddAccountsThreshold) &&
-// (numberOfTokensAdded > autoAddTokensThreshold)
-// const userIsNotInBeta = !state.metamask.featureFlags.betaUI
-
-// return userIsNotInBeta && userPassesThreshold
-// }
-
function getAddressBook (state) {
return state.metamask.addressBook
}
@@ -130,11 +118,11 @@ function getForceGasMin (state) {
}
function getGasLimit (state) {
- return state.metamask.send.gasLimit
+ return state.metamask.send.gasLimit || '0'
}
function getGasPrice (state) {
- return state.metamask.send.gasPrice
+ return state.metamask.send.gasPrice || getFastPriceEstimateInHexWEI(state)
}
function getGasPriceFromRecentBlocks (state) {
@@ -142,7 +130,7 @@ function getGasPriceFromRecentBlocks (state) {
}
function getGasTotal (state) {
- return state.metamask.send.gasTotal
+ return calcGasTotal(getGasLimit(state), getGasPrice(state))
}
function getPrimaryCurrency (state) {
@@ -155,14 +143,14 @@ function getRecentBlocks (state) {
}
function getSelectedAccount (state) {
- const accounts = state.metamask.accounts
+ const accounts = getMetaMaskAccounts(state)
const selectedAddress = getSelectedAddress(state)
return accounts[selectedAddress]
}
function getSelectedAddress (state) {
- const selectedAddress = state.metamask.selectedAddress || Object.keys(state.metamask.accounts)[0]
+ const selectedAddress = state.metamask.selectedAddress || Object.keys(getMetaMaskAccounts(state))[0]
return selectedAddress
}
@@ -263,6 +251,10 @@ function getSendToAccounts (state) {
return Object.entries(allAccounts).map(([key, account]) => account)
}
+function getSendWarnings (state) {
+ return state.send.warnings
+}
+
function getTokenBalance (state) {
return state.metamask.send.tokenBalance
}
diff --git a/ui/app/components/send/send.utils.js b/ui/app/components/app/send/send.utils.js
index eb1667c63..7609d46ea 100644
--- a/ui/app/components/send/send.utils.js
+++ b/ui/app/components/app/send/send.utils.js
@@ -5,10 +5,10 @@ const {
multiplyCurrencies,
conversionGreaterThan,
conversionLessThan,
-} = require('../../conversion-util')
+} = require('../../../helpers/utils/conversion-util')
const {
calcTokenAmount,
-} = require('../../token-util')
+} = require('../../../helpers/utils/token-util')
const {
BASE_TOKEN_GAS_COST,
INSUFFICIENT_FUNDS_ERROR,
@@ -89,11 +89,10 @@ function isTokenBalanceSufficient ({
const tokenBalanceIsSufficient = conversionGTE(
{
value: tokenBalance,
- fromNumericBase: 'dec',
+ fromNumericBase: 'hex',
},
{
value: calcTokenAmount(amountInDec, decimals),
- fromNumericBase: 'dec',
},
)
@@ -151,7 +150,6 @@ function getAmountErrorObject ({
}
function getGasFeeErrorObject ({
- amount,
amountConversionRate,
balance,
conversionRate,
@@ -180,7 +178,7 @@ function getGasFeeErrorObject ({
function calcTokenBalance ({ selectedToken, usersToken }) {
const { decimals } = selectedToken || {}
- return calcTokenAmount(usersToken.balance.toString(), decimals) + ''
+ return calcTokenAmount(usersToken.balance.toString(), decimals).toString(16)
}
function doesAmountErrorRequireUpdate ({
@@ -236,10 +234,14 @@ async function estimateGas ({
if (to) {
paramsForGasEstimate.to = to
}
+
+ if (!value || value === '0') {
+ paramsForGasEstimate.value = '0xff'
+ }
}
// if not, fall back to block gasLimit
- paramsForGasEstimate.gas = ethUtil.addHexPrefix(multiplyCurrencies(blockGasLimit, 0.95, {
+ paramsForGasEstimate.gas = ethUtil.addHexPrefix(multiplyCurrencies(blockGasLimit || '0x5208', 0.95, {
multiplicandBase: 16,
multiplierBase: 10,
roundDown: '0',
diff --git a/ui/app/components/send/tests/send-component.test.js b/ui/app/components/app/send/tests/send-component.test.js
index f4943e707..738c14839 100644
--- a/ui/app/components/send/tests/send-component.test.js
+++ b/ui/app/components/app/send/tests/send-component.test.js
@@ -3,16 +3,23 @@ import assert from 'assert'
import proxyquire from 'proxyquire'
import { shallow } from 'enzyme'
import sinon from 'sinon'
+import timeout from '../../../../../lib/test-timeout'
import SendHeader from '../send-header/send-header.container'
import SendContent from '../send-content/send-content.component'
import SendFooter from '../send-footer/send-footer.container'
+const mockBasicGasEstimates = {
+ blockTime: 'mockBlockTime',
+}
+
const propsMethodSpies = {
- updateAndSetGasTotal: sinon.spy(),
+ updateAndSetGasLimit: sinon.spy(),
updateSendErrors: sinon.spy(),
updateSendTokenBalance: sinon.spy(),
resetSendState: sinon.spy(),
+ fetchBasicGasEstimates: sinon.stub().returns(Promise.resolve(mockBasicGasEstimates)),
+ fetchGasEstimates: sinon.spy(),
}
const utilsMethodStubs = {
getAmountErrorObject: sinon.stub().returns({ amount: 'mockAmountError' }),
@@ -37,6 +44,8 @@ describe('Send Component', function () {
blockGasLimit={'mockBlockGasLimit'}
conversionRate={10}
editingTransactionId={'mockEditingTransactionId'}
+ fetchBasicGasEstimates={propsMethodSpies.fetchBasicGasEstimates}
+ fetchGasEstimates={propsMethodSpies.fetchGasEstimates}
from={ { address: 'mockAddress', balance: 'mockBalance' } }
gasLimit={'mockGasLimit'}
gasPrice={'mockGasPrice'}
@@ -50,7 +59,7 @@ describe('Send Component', function () {
showHexData={true}
tokenBalance={'mockTokenBalance'}
tokenContract={'mockTokenContract'}
- updateAndSetGasTotal={propsMethodSpies.updateAndSetGasTotal}
+ updateAndSetGasLimit={propsMethodSpies.updateAndSetGasLimit}
updateSendErrors={propsMethodSpies.updateSendErrors}
updateSendTokenBalance={propsMethodSpies.updateSendTokenBalance}
resetSendState={propsMethodSpies.resetSendState}
@@ -63,7 +72,8 @@ describe('Send Component', function () {
utilsMethodStubs.doesAmountErrorRequireUpdate.resetHistory()
utilsMethodStubs.getAmountErrorObject.resetHistory()
utilsMethodStubs.getGasFeeErrorObject.resetHistory()
- propsMethodSpies.updateAndSetGasTotal.resetHistory()
+ propsMethodSpies.fetchBasicGasEstimates.resetHistory()
+ propsMethodSpies.updateAndSetGasLimit.resetHistory()
propsMethodSpies.updateSendErrors.resetHistory()
propsMethodSpies.updateSendTokenBalance.resetHistory()
})
@@ -72,12 +82,20 @@ describe('Send Component', function () {
assert(SendTransactionScreen.prototype.componentDidMount.calledOnce)
})
- describe('componentWillMount', () => {
- it('should call this.updateGas', () => {
+ describe('componentDidMount', () => {
+ it('should call props.fetchBasicGasAndTimeEstimates', () => {
+ propsMethodSpies.fetchBasicGasEstimates.resetHistory()
+ assert.equal(propsMethodSpies.fetchBasicGasEstimates.callCount, 0)
+ wrapper.instance().componentDidMount()
+ assert.equal(propsMethodSpies.fetchBasicGasEstimates.callCount, 1)
+ })
+
+ it('should call this.updateGas', async () => {
SendTransactionScreen.prototype.updateGas.resetHistory()
propsMethodSpies.updateSendErrors.resetHistory()
assert.equal(SendTransactionScreen.prototype.updateGas.callCount, 0)
- wrapper.instance().componentWillMount()
+ wrapper.instance().componentDidMount()
+ await timeout(250)
assert.equal(SendTransactionScreen.prototype.updateGas.callCount, 1)
})
})
@@ -158,14 +176,12 @@ describe('Send Component', function () {
assert.deepEqual(
utilsMethodStubs.getGasFeeErrorObject.getCall(0).args[0],
{
- amount: 'mockAmount',
amountConversionRate: 'mockAmountConversionRate',
balance: 'mockBalance',
conversionRate: 10,
gasTotal: 'mockGasTotal',
primaryCurrency: 'mockPrimaryCurrency',
selectedToken: 'mockSelectedToken',
- tokenBalance: 'mockTokenBalance',
}
)
})
@@ -273,12 +289,12 @@ describe('Send Component', function () {
})
describe('updateGas', () => {
- it('should call updateAndSetGasTotal with the correct params if no to prop is passed', () => {
- propsMethodSpies.updateAndSetGasTotal.resetHistory()
+ it('should call updateAndSetGasLimit with the correct params if no to prop is passed', () => {
+ propsMethodSpies.updateAndSetGasLimit.resetHistory()
wrapper.instance().updateGas()
- assert.equal(propsMethodSpies.updateAndSetGasTotal.callCount, 1)
+ assert.equal(propsMethodSpies.updateAndSetGasLimit.callCount, 1)
assert.deepEqual(
- propsMethodSpies.updateAndSetGasTotal.getCall(0).args[0],
+ propsMethodSpies.updateAndSetGasLimit.getCall(0).args[0],
{
blockGasLimit: 'mockBlockGasLimit',
editingTransactionId: 'mockEditingTransactionId',
@@ -294,20 +310,20 @@ describe('Send Component', function () {
)
})
- it('should call updateAndSetGasTotal with the correct params if a to prop is passed', () => {
- propsMethodSpies.updateAndSetGasTotal.resetHistory()
+ it('should call updateAndSetGasLimit with the correct params if a to prop is passed', () => {
+ propsMethodSpies.updateAndSetGasLimit.resetHistory()
wrapper.setProps({ to: 'someAddress' })
wrapper.instance().updateGas()
assert.equal(
- propsMethodSpies.updateAndSetGasTotal.getCall(0).args[0].to,
+ propsMethodSpies.updateAndSetGasLimit.getCall(0).args[0].to,
'someaddress',
)
})
- it('should call updateAndSetGasTotal with to set to lowercase if passed', () => {
- propsMethodSpies.updateAndSetGasTotal.resetHistory()
+ it('should call updateAndSetGasLimit with to set to lowercase if passed', () => {
+ propsMethodSpies.updateAndSetGasLimit.resetHistory()
wrapper.instance().updateGas({ to: '0xABC' })
- assert.equal(propsMethodSpies.updateAndSetGasTotal.getCall(0).args[0].to, '0xabc')
+ assert.equal(propsMethodSpies.updateAndSetGasLimit.getCall(0).args[0].to, '0xabc')
})
})
diff --git a/ui/app/components/send/tests/send-container.test.js b/ui/app/components/app/send/tests/send-container.test.js
index 6aa4bf826..9538b67b3 100644
--- a/ui/app/components/send/tests/send-container.test.js
+++ b/ui/app/components/app/send/tests/send-container.test.js
@@ -47,8 +47,8 @@ proxyquire('../send.container.js', {
getTokenBalance: (s) => `mockTokenBalance:${s}`,
getQrCodeData: (s) => `mockQrCodeData:${s}`,
},
- '../../actions': actionSpies,
- '../../ducks/send.duck': duckActionSpies,
+ '../../../store/actions': actionSpies,
+ '../../../ducks/send/send.duck': duckActionSpies,
'./send.utils.js': {
calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice,
},
@@ -94,7 +94,7 @@ describe('send container', () => {
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
})
- describe('updateAndSetGasTotal()', () => {
+ describe('updateAndSetGasLimit()', () => {
const mockProps = {
blockGasLimit: 'mockBlockGasLimit',
editingTransactionId: '0x2',
@@ -109,7 +109,7 @@ describe('send container', () => {
}
it('should dispatch a setGasTotal action when editingTransactionId is truthy', () => {
- mapDispatchToPropsObject.updateAndSetGasTotal(mockProps)
+ mapDispatchToPropsObject.updateAndSetGasLimit(mockProps)
assert(dispatchSpy.calledOnce)
assert.equal(
actionSpies.setGasTotal.getCall(0).args[0],
@@ -118,14 +118,14 @@ describe('send container', () => {
})
it('should dispatch an updateGasData action when editingTransactionId is falsy', () => {
- const { selectedAddress, selectedToken, recentBlocks, blockGasLimit, to, value, data } = mockProps
- mapDispatchToPropsObject.updateAndSetGasTotal(
+ const { gasPrice, selectedAddress, selectedToken, recentBlocks, blockGasLimit, to, value, data } = mockProps
+ mapDispatchToPropsObject.updateAndSetGasLimit(
Object.assign({}, mockProps, {editingTransactionId: false})
)
assert(dispatchSpy.calledOnce)
assert.deepEqual(
actionSpies.updateGasData.getCall(0).args[0],
- { selectedAddress, selectedToken, recentBlocks, blockGasLimit, to, value, data }
+ { gasPrice, selectedAddress, selectedToken, recentBlocks, blockGasLimit, to, value, data }
)
})
})
diff --git a/ui/app/components/send/tests/send-selectors-test-data.js b/ui/app/components/app/send/tests/send-selectors-test-data.js
index 30a2666cf..d43d7c650 100644
--- a/ui/app/components/send/tests/send-selectors-test-data.js
+++ b/ui/app/components/app/send/tests/send-selectors-test-data.js
@@ -2,7 +2,7 @@ module.exports = {
'metamask': {
'isInitialized': true,
'isUnlocked': true,
- 'featureFlags': {'betaUI': true, 'sendHexData': true},
+ 'featureFlags': {'sendHexData': true},
'rpcTarget': 'https://rawtestrpc.metamask.io/',
'identities': {
'0xfdea65c8e26263f6d9a1b5de9555d2931a33b825': {
@@ -22,6 +22,7 @@ module.exports = {
'name': 'Send Account 4',
},
},
+ 'cachedBalances': {},
'currentBlockGasLimit': '0x4c1878',
'currentCurrency': 'USD',
'conversionRate': 1200.88200327,
diff --git a/ui/app/components/send/tests/send-selectors.test.js b/ui/app/components/app/send/tests/send-selectors.test.js
index e7e901f0d..cdc86fe59 100644
--- a/ui/app/components/send/tests/send-selectors.test.js
+++ b/ui/app/components/app/send/tests/send-selectors.test.js
@@ -237,7 +237,7 @@ describe('send selectors', () => {
it('should return the send.gasTotal', () => {
assert.equal(
getGasTotal(mockState),
- '0xb451dc41b578'
+ 'a9ff56'
)
})
})
diff --git a/ui/app/components/send/tests/send-utils.test.js b/ui/app/components/app/send/tests/send-utils.test.js
index b72d87eee..fc4c6deed 100644
--- a/ui/app/components/send/tests/send-utils.test.js
+++ b/ui/app/components/app/send/tests/send-utils.test.js
@@ -9,7 +9,7 @@ import {
const {
addCurrencies,
subtractCurrencies,
-} = require('../../../conversion-util')
+} = require('../../../../helpers/utils/conversion-util')
const {
INSUFFICIENT_FUNDS_ERROR,
@@ -32,7 +32,7 @@ const stubs = {
}
const sendUtils = proxyquire('../send.utils.js', {
- '../../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,
},
- '../../token-util': { calcTokenAmount: stubs.calcTokenAmount },
+ '../../../helpers/utils/token-util': { calcTokenAmount: stubs.calcTokenAmount },
'ethereumjs-abi': {
rawEncode: stubs.rawEncode,
},
@@ -285,11 +285,10 @@ describe('send utils', () => {
[
{
value: 123,
- fromNumericBase: 'dec',
+ fromNumericBase: 'hex',
},
{
value: 'calc:1610',
- fromNumericBase: 'dec',
},
]
)
@@ -317,6 +316,7 @@ describe('send utils', () => {
from: 'mockAddress',
gas: '0x64x0.95',
to: '0xisContract',
+ value: '0xff',
}
beforeEach(() => {
@@ -374,7 +374,7 @@ describe('send utils', () => {
assert.equal(baseMockParams.estimateGasMethod.callCount, 1)
assert.deepEqual(
baseMockParams.estimateGasMethod.getCall(0).args[0],
- { gasPrice: undefined, value: undefined, data, from: baseExpectedCall.from, gas: baseExpectedCall.gas},
+ { gasPrice: undefined, value: '0xff', data, from: baseExpectedCall.from, gas: baseExpectedCall.gas},
)
assert.equal(result, '0xabc16')
})
diff --git a/ui/app/components/send/to-autocomplete.component.js b/ui/app/components/app/send/to-autocomplete.component.js
index 9e270db75..183967c58 100644
--- a/ui/app/components/send/to-autocomplete.component.js
+++ b/ui/app/components/app/send/to-autocomplete.component.js
@@ -1,7 +1,7 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-import AccountListItem from '../send/account-list-item/account-list-item.component'
+import AccountListItem from './account-list-item/account-list-item.component'
export default class ToAutoComplete extends Component {
diff --git a/ui/app/components/send/to-autocomplete/index.js b/ui/app/components/app/send/to-autocomplete/index.js
index 244d301d1..244d301d1 100644
--- a/ui/app/components/send/to-autocomplete/index.js
+++ b/ui/app/components/app/send/to-autocomplete/index.js
diff --git a/ui/app/components/send/to-autocomplete/to-autocomplete.js b/ui/app/components/app/send/to-autocomplete/to-autocomplete.js
index 39d15dfa7..d3db8cb59 100644
--- a/ui/app/components/send/to-autocomplete/to-autocomplete.js
+++ b/ui/app/components/app/send/to-autocomplete/to-autocomplete.js
@@ -4,8 +4,8 @@ const h = require('react-hyperscript')
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('../../tooltip')
-const checksumAddress = require('../../../util').checksumAddress
+const Tooltip = require('../../../ui/tooltip')
+const checksumAddress = require('../../../../helpers/utils/util').checksumAddress
ToAutoComplete.contextTypes = {
t: PropTypes.func,
diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/app/shapeshift-form.js
index a842bcc8b..11459fd5e 100644
--- a/ui/app/components/shapeshift-form.js
+++ b/ui/app/components/app/shapeshift-form.js
@@ -4,12 +4,12 @@ const PropTypes = require('prop-types')
const Component = require('react').Component
const connect = require('react-redux').connect
const classnames = require('classnames')
-const { qrcode } = require('qrcode-npm')
-const { shapeShiftSubview, pairUpdate, buyWithShapeShift } = require('../actions')
-const { isValidAddress } = require('../util')
+const qrcode = require('qrcode-generator')
+const { shapeShiftSubview, pairUpdate, buyWithShapeShift } = require('../../store/actions')
+const { isValidAddress } = require('../../helpers/utils/util')
const SimpleDropdown = require('./dropdowns/simple-dropdown')
-import Button from './button'
+import Button from '../ui/button'
function mapStateToProps (state) {
const {
diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/app/shift-list-item.js
index 0461b615a..f5fa00047 100644
--- a/ui/app/components/shift-list-item.js
+++ b/ui/app/components/app/shift-list-item.js
@@ -3,14 +3,13 @@ const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
-const vreme = new (require('vreme'))()
const explorerLink = require('etherscan-link').createExplorerLink
-const actions = require('../actions')
-const addressSummary = require('../util').addressSummary
+const actions = require('../../store/actions')
+const { formatDate, addressSummary } = require('../../helpers/utils/util')
-const CopyButton = require('./copyButton')
-const EthBalance = require('./eth-balance')
-const Tooltip = require('./tooltip')
+const CopyButton = require('../ui/copyButton')
+const EthBalance = require('../ui/eth-balance')
+const Tooltip = require('../ui/tooltip')
ShiftListItem.contextTypes = {
@@ -67,10 +66,6 @@ ShiftListItem.prototype.render = function () {
])
}
-function formatDate (date) {
- return vreme.format(new Date(date), 'March 16 2014 14:30')
-}
-
ShiftListItem.prototype.renderUtilComponents = function () {
var props = this.props
const { conversionRate, currentCurrency } = props
diff --git a/ui/app/components/sidebars/index.js b/ui/app/components/app/sidebars/index.js
index 732925f69..732925f69 100644
--- a/ui/app/components/sidebars/index.js
+++ b/ui/app/components/app/sidebars/index.js
diff --git a/ui/app/components/sidebars/index.scss b/ui/app/components/app/sidebars/index.scss
index 5ab0664df..08181426f 100644
--- a/ui/app/components/sidebars/index.scss
+++ b/ui/app/components/app/sidebars/index.scss
@@ -1,3 +1,5 @@
+@import 'sidebar-content';
+
.sidebar-right-enter {
transition: transform 300ms ease-in-out;
transform: translateX(-100%);
@@ -58,6 +60,11 @@
width: 408px;
left: calc(100% - 408px);
}
+
+ @media screen and (max-width: $break-small) {
+ width: 100%;
+ left: 0%;
+ }
}
.sidebar-overlay {
@@ -71,4 +78,4 @@
opacity: 1;
visibility: visible;
background-color: rgba(0, 0, 0, .3);
-} \ No newline at end of file
+}
diff --git a/ui/app/components/app/sidebars/sidebar-content.scss b/ui/app/components/app/sidebars/sidebar-content.scss
new file mode 100644
index 000000000..ca6b0a458
--- /dev/null
+++ b/ui/app/components/app/sidebars/sidebar-content.scss
@@ -0,0 +1,112 @@
+.sidebar-left {
+ display: flex;
+
+ .gas-modal-page-container {
+ display: flex;
+
+ .page-container {
+ flex: 1;
+ max-width: 100%;
+
+ &__content {
+ display: flex;
+ overflow-y: initial;
+ }
+
+ @media screen and (max-width: $break-small) {
+ max-width: 344px;
+ min-height: auto;
+ }
+
+ @media screen and (min-width: $break-small) {
+ max-height: none;
+ }
+ }
+
+ .gas-price-chart {
+ margin-left: 10px;
+
+ &__root {
+ max-height: 160px !important;
+ }
+ }
+
+ .page-container__bottom {
+ display: flex;
+ flex-direction: column;
+ flex-flow: space-between;
+ height: 100%;
+ }
+
+ .page-container__content {
+ overflow-y: inherit;
+ }
+
+ .basic-tab-content {
+ height: auto;
+ margin-bottom: 0px;
+ border-bottom: 1px solid #d2d8dd;
+ flex: 1 1 70%;
+
+ @media screen and (max-width: $break-small) {
+ padding-left: 14px;
+ padding-bottom: 21px;
+ }
+
+ .gas-price-button-group--alt {
+ @media screen and (max-width: $break-small) {
+ max-width: 318px;
+
+ &__time-estimate {
+ font-size: 12px;
+ }
+ }
+ }
+ }
+
+ .advanced-tab {
+ @media screen and (min-width: $break-small) {
+ flex: 1 1 70%;
+ }
+
+ &__fee-chart {
+ height: 320px;
+
+ @media screen and (max-width: $break-small) {
+ height: initial;
+ }
+ }
+
+ &__fee-chart__speed-buttons {
+ bottom: 77px;
+
+ @media screen and (max-width: $break-small) {
+ display: none;
+ }
+ }
+ }
+
+ .gas-modal-content {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+
+ &__info-row-wrapper {
+ display: flex;
+ @media screen and (min-width: $break-small) {
+ flex: 1 1 30%;
+ }
+ }
+
+ &__info-row {
+ height: 170px;
+
+ @media screen and (max-width: $break-small) {
+ height: initial;
+ display: flex;
+ justify-content: center;
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/ui/app/components/app/sidebars/sidebar.component.js b/ui/app/components/app/sidebars/sidebar.component.js
new file mode 100644
index 000000000..b9e0f9e81
--- /dev/null
+++ b/ui/app/components/app/sidebars/sidebar.component.js
@@ -0,0 +1,69 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
+import WalletView from '../wallet-view'
+import { WALLET_VIEW_SIDEBAR } from './sidebar.constants'
+import CustomizeGas from '../gas-customization/gas-modal-page-container/'
+
+export default class Sidebar extends Component {
+
+ static propTypes = {
+ sidebarOpen: PropTypes.bool,
+ hideSidebar: PropTypes.func,
+ sidebarShouldClose: PropTypes.bool,
+ transitionName: PropTypes.string,
+ type: PropTypes.string,
+ sidebarProps: PropTypes.object,
+ onOverlayClose: PropTypes.func,
+ };
+
+ renderOverlay () {
+ const { onOverlayClose } = this.props
+
+ return <div
+ className="sidebar-overlay"
+ onClick={() => {
+ onOverlayClose && onOverlayClose()
+ this.props.hideSidebar()
+ }
+ } />
+ }
+
+ renderSidebarContent () {
+ const { type, sidebarProps = {} } = this.props
+ const { transaction = {} } = sidebarProps
+ switch (type) {
+ case WALLET_VIEW_SIDEBAR:
+ return <WalletView responsiveDisplayClassname={'sidebar-right' } />
+ case 'customize-gas':
+ return <div className={'sidebar-left'}><CustomizeGas transaction={transaction} /></div>
+ default:
+ return null
+ }
+
+ }
+
+ componentDidUpdate (prevProps) {
+ if (!prevProps.sidebarShouldClose && this.props.sidebarShouldClose) {
+ this.props.hideSidebar()
+ }
+ }
+
+ render () {
+ const { transitionName, sidebarOpen, sidebarShouldClose } = this.props
+
+ return (
+ <div>
+ <ReactCSSTransitionGroup
+ transitionName={transitionName}
+ transitionEnterTimeout={300}
+ transitionLeaveTimeout={200}
+ >
+ { sidebarOpen && !sidebarShouldClose ? this.renderSidebarContent() : null }
+ </ReactCSSTransitionGroup>
+ { sidebarOpen && !sidebarShouldClose ? this.renderOverlay() : null }
+ </div>
+ )
+ }
+
+}
diff --git a/ui/app/components/sidebars/sidebar.constants.js b/ui/app/components/app/sidebars/sidebar.constants.js
index 1613a8245..1613a8245 100644
--- a/ui/app/components/sidebars/sidebar.constants.js
+++ b/ui/app/components/app/sidebars/sidebar.constants.js
diff --git a/ui/app/components/sidebars/tests/sidebars-component.test.js b/ui/app/components/app/sidebars/tests/sidebars-component.test.js
index e2d77518a..cee22aca8 100644
--- a/ui/app/components/sidebars/tests/sidebars-component.test.js
+++ b/ui/app/components/app/sidebars/tests/sidebars-component.test.js
@@ -6,6 +6,7 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
import Sidebar from '../sidebar.component.js'
import WalletView from '../../wallet-view'
+import CustomizeGas from '../../gas-customization/gas-modal-page-container/'
const propsMethodSpies = {
hideSidebar: sinon.spy(),
@@ -59,6 +60,14 @@ describe('Sidebar Component', function () {
assert.equal(renderSidebarContent.props.responsiveDisplayClassname, 'sidebar-right')
})
+ it('should render sidebar content with the correct props', () => {
+ wrapper.setProps({ type: 'customize-gas' })
+ renderSidebarContent = wrapper.instance().renderSidebarContent()
+ const renderedSidebarContent = shallow(renderSidebarContent)
+ assert(renderedSidebarContent.hasClass('sidebar-left'))
+ assert(renderedSidebarContent.childAt(0).is(CustomizeGas))
+ })
+
it('should not render with an unrecognized type', () => {
wrapper.setProps({ type: 'foobar' })
renderSidebarContent = wrapper.instance().renderSidebarContent()
diff --git a/ui/app/components/signature-request.js b/ui/app/components/app/signature-request.js
index 85af3b00b..e47791b67 100644
--- a/ui/app/components/signature-request.js
+++ b/ui/app/components/app/signature-request.js
@@ -2,7 +2,9 @@ const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
-import Identicon from './identicon'
+import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums'
+import { getEnvironmentType } from '../../../../app/scripts/lib/util'
+import Identicon from '../ui/identicon'
const connect = require('react-redux').connect
const ethUtil = require('ethereumjs-util')
const classnames = require('classnames')
@@ -10,10 +12,10 @@ const { compose } = require('recompose')
const { withRouter } = require('react-router-dom')
const { ObjectInspector } = require('react-inspector')
-const AccountDropdownMini = require('./dropdowns/account-dropdown-mini')
+import AccountDropdownMini from '../ui/account-dropdown-mini'
-const actions = require('../actions')
-const { conversionUtil } = require('../conversion-util')
+const actions = require('../../store/actions')
+const { conversionUtil } = require('../../helpers/utils/conversion-util')
const {
getSelectedAccount,
@@ -21,12 +23,12 @@ const {
getSelectedAddress,
accountsWithSendEtherInfoSelector,
conversionRateSelector,
-} = require('../selectors.js')
+} = require('../../selectors/selectors.js')
-import { clearConfirmTransaction } from '../ducks/confirm-transaction.duck'
-import Button from './button'
+import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck'
+import Button from '../ui/button'
-const { DEFAULT_ROUTE } = require('../routes')
+const { DEFAULT_ROUTE } = require('../../helpers/constants/routes')
function mapStateToProps (state) {
return {
@@ -47,13 +49,50 @@ function mapDispatchToProps (dispatch) {
}
}
+function mergeProps (stateProps, dispatchProps, ownProps) {
+ const {
+ signPersonalMessage,
+ signTypedMessage,
+ cancelPersonalMessage,
+ cancelTypedMessage,
+ signMessage,
+ cancelMessage,
+ txData,
+ } = ownProps
+
+ const { type } = txData
+
+ let cancel
+ let sign
+ if (type === 'personal_sign') {
+ cancel = cancelPersonalMessage
+ sign = signPersonalMessage
+ } else if (type === 'eth_signTypedData') {
+ cancel = cancelTypedMessage
+ sign = signTypedMessage
+ } else if (type === 'eth_sign') {
+ cancel = cancelMessage
+ sign = signMessage
+ }
+
+ return {
+ ...stateProps,
+ ...dispatchProps,
+ ...ownProps,
+ txData,
+ cancel,
+ sign,
+ }
+}
+
SignatureRequest.contextTypes = {
t: PropTypes.func,
+ metricsEvent: PropTypes.func,
}
module.exports = compose(
withRouter,
- connect(mapStateToProps, mapDispatchToProps)
+ connect(mapStateToProps, mapDispatchToProps, mergeProps)
)(SignatureRequest)
@@ -63,7 +102,24 @@ function SignatureRequest (props) {
this.state = {
selectedAccount: props.selectedAccount,
- accountDropdownOpen: false,
+ }
+}
+
+SignatureRequest.prototype.componentDidMount = function () {
+ const { clearConfirmTransaction, cancel } = this.props
+ const { metricsEvent } = this.context
+ if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) {
+ window.onbeforeunload = event => {
+ metricsEvent({
+ eventOpts: {
+ category: 'Transactions',
+ action: 'Sign Request',
+ name: 'Cancel Sig Request Via Notification Close',
+ },
+ })
+ clearConfirmTransaction()
+ cancel(event)
+ }
}
}
@@ -82,10 +138,7 @@ SignatureRequest.prototype.renderHeader = function () {
}
SignatureRequest.prototype.renderAccountDropdown = function () {
- const {
- selectedAccount,
- accountDropdownOpen,
- } = this.state
+ const { selectedAccount } = this.state
const {
accounts,
@@ -98,10 +151,7 @@ SignatureRequest.prototype.renderAccountDropdown = function () {
h(AccountDropdownMini, {
selectedAccount,
accounts,
- onSelect: selectedAccount => this.setState({ selectedAccount }),
- dropdownOpen: accountDropdownOpen,
- openDropdown: () => this.setState({ accountDropdownOpen: true }),
- closeDropdown: () => this.setState({ accountDropdownOpen: false }),
+ disabled: true,
}),
])
@@ -164,7 +214,7 @@ SignatureRequest.prototype.msgHexToText = function (hex) {
try {
const stripped = ethUtil.stripHexPrefix(hex)
const buff = Buffer.from(stripped, 'hex')
- return buff.toString('utf8')
+ return buff.length === 32 ? hex : buff.toString('utf8')
} catch (e) {
return hex
}
@@ -239,30 +289,7 @@ SignatureRequest.prototype.renderBody = function () {
}
SignatureRequest.prototype.renderFooter = function () {
- const {
- signPersonalMessage,
- signTypedMessage,
- cancelPersonalMessage,
- cancelTypedMessage,
- signMessage,
- cancelMessage,
- } = this.props
-
- const { txData } = this.props
- const { type } = txData
-
- let cancel
- let sign
- if (type === 'personal_sign') {
- cancel = cancelPersonalMessage
- sign = signPersonalMessage
- } else if (type === 'eth_signTypedData') {
- cancel = cancelTypedMessage
- sign = signTypedMessage
- } else if (type === 'eth_sign') {
- cancel = cancelMessage
- sign = signMessage
- }
+ const { cancel, sign } = this.props
return h('div.request-signature__footer', [
h(Button, {
@@ -271,6 +298,13 @@ SignatureRequest.prototype.renderFooter = function () {
className: 'request-signature__footer__cancel-button',
onClick: event => {
cancel(event).then(() => {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Transactions',
+ action: 'Sign Request',
+ name: 'Cancel',
+ },
+ })
this.props.clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE)
})
@@ -279,8 +313,16 @@ SignatureRequest.prototype.renderFooter = function () {
h(Button, {
type: 'primary',
large: true,
+ className: 'request-signature__footer__sign-button',
onClick: event => {
sign(event).then(() => {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Transactions',
+ action: 'Sign Request',
+ name: 'Confirm',
+ },
+ })
this.props.clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE)
})
diff --git a/ui/app/components/app/tab-bar.js b/ui/app/components/app/tab-bar.js
new file mode 100644
index 000000000..43923989a
--- /dev/null
+++ b/ui/app/components/app/tab-bar.js
@@ -0,0 +1,37 @@
+import React, { Component } from 'react'
+const PropTypes = require('prop-types')
+const classnames = require('classnames')
+
+class TabBar extends Component {
+ render () {
+ const { tabs = [], onSelect, isActive } = this.props
+
+ return (
+ <div className="tab-bar">
+ {tabs.map(({ key, content, description }) => (
+ <div
+ key={key}
+ className={classnames('tab-bar__tab pointer', {
+ 'tab-bar__tab--active': isActive(key, content),
+ })}
+ onClick={() => onSelect(key)}
+ >
+ <div className="tab-bar__tab__content">
+ <div className="tab-bar__tab__content__title">{content}</div>
+ <div className="tab-bar__tab__content__description">{description}</div>
+ </div>
+ <div className="tab-bar__tab__caret" />
+ </div>
+ ))}
+ </div>
+ )
+ }
+}
+
+TabBar.propTypes = {
+ isActive: PropTypes.func.isRequired,
+ tabs: PropTypes.array,
+ onSelect: PropTypes.func,
+}
+
+module.exports = TabBar
diff --git a/ui/app/components/token-cell.js b/ui/app/components/app/token-cell.js
index 75ba347fa..cef809e8a 100644
--- a/ui/app/components/token-cell.js
+++ b/ui/app/components/app/token-cell.js
@@ -1,12 +1,13 @@
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
-import Identicon from './identicon'
-const prefixForNetwork = require('../../lib/etherscan-prefix-for-network')
-const selectors = require('../selectors')
-const actions = require('../actions')
-const { conversionUtil, multiplyCurrencies } = require('../conversion-util')
+import Identicon from '../ui/identicon'
+const prefixForNetwork = require('../../../lib/etherscan-prefix-for-network')
+const selectors = require('../../selectors/selectors')
+const actions = require('../../store/actions')
+const { conversionUtil, multiplyCurrencies } = require('../../helpers/utils/conversion-util')
const TokenMenuDropdown = require('./dropdowns/token-menu-dropdown.js')
@@ -40,6 +41,10 @@ function TokenCell () {
}
}
+TokenCell.contextTypes = {
+ metricsEvent: PropTypes.func,
+}
+
TokenCell.prototype.render = function () {
const { tokenMenuOpen } = this.state
const props = this.props
@@ -88,6 +93,13 @@ TokenCell.prototype.render = function () {
// onClick: this.view.bind(this, address, userAddress, network),
onClick: () => {
setSelectedToken(address)
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Token Menu',
+ name: 'Clicked Token',
+ },
+ })
selectedTokenAddress !== address && sidebarOpen && hideSidebar()
},
}, [
diff --git a/ui/app/components/token-list.js b/ui/app/components/app/token-list.js
index 6a88f30bf..2188e7020 100644
--- a/ui/app/components/token-list.js
+++ b/ui/app/components/app/token-list.js
@@ -5,7 +5,7 @@ const inherits = require('util').inherits
const TokenTracker = require('eth-token-tracker')
const TokenCell = require('./token-cell.js')
const connect = require('react-redux').connect
-const selectors = require('../selectors')
+const selectors = require('../../selectors/selectors')
const log = require('loglevel')
function mapStateToProps (state) {
@@ -134,17 +134,17 @@ TokenList.prototype.createFreshTokenTracker = function () {
})
}
-TokenList.prototype.componentDidUpdate = function (nextProps) {
+TokenList.prototype.componentDidUpdate = function (prevProps) {
const {
network: oldNet,
userAddress: oldAddress,
tokens,
- } = this.props
+ } = prevProps
const {
network: newNet,
userAddress: newAddress,
tokens: newTokens,
- } = nextProps
+ } = this.props
const isLoading = newNet === 'loading'
const missingInfo = !oldNet || !newNet || !oldAddress || !newAddress
diff --git a/ui/app/components/transaction-action/index.js b/ui/app/components/app/transaction-action/index.js
index a6e9097f1..a6e9097f1 100644
--- a/ui/app/components/transaction-action/index.js
+++ b/ui/app/components/app/transaction-action/index.js
diff --git a/ui/app/components/transaction-action/tests/transaction-action.component.test.js b/ui/app/components/app/transaction-action/tests/transaction-action.component.test.js
index b22a9db39..b22a9db39 100644
--- a/ui/app/components/transaction-action/tests/transaction-action.component.test.js
+++ b/ui/app/components/app/transaction-action/tests/transaction-action.component.test.js
diff --git a/ui/app/components/transaction-action/transaction-action.component.js b/ui/app/components/app/transaction-action/transaction-action.component.js
index 1de91cb71..4a5efdaae 100644
--- a/ui/app/components/transaction-action/transaction-action.component.js
+++ b/ui/app/components/app/transaction-action/transaction-action.component.js
@@ -1,8 +1,8 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-import { getTransactionActionKey } from '../../helpers/transactions.util'
-import { camelCaseToCapitalize } from '../../helpers/common.util'
+import { getTransactionActionKey } from '../../../helpers/utils/transactions.util'
+import { camelCaseToCapitalize } from '../../../helpers/utils/common.util'
export default class TransactionAction extends PureComponent {
static contextTypes = {
diff --git a/ui/app/components/transaction-activity-log/index.js b/ui/app/components/app/transaction-activity-log/index.js
index a33da15a3..a33da15a3 100644
--- a/ui/app/components/transaction-activity-log/index.js
+++ b/ui/app/components/app/transaction-activity-log/index.js
diff --git a/ui/app/components/transaction-activity-log/index.scss b/ui/app/components/app/transaction-activity-log/index.scss
index 27f3006b3..00c17e6aa 100644
--- a/ui/app/components/transaction-activity-log/index.scss
+++ b/ui/app/components/app/transaction-activity-log/index.scss
@@ -1,7 +1,8 @@
.transaction-activity-log {
- &__card {
- background: $white;
- height: 100%;
+ &__title {
+ border-bottom: 1px solid #d8d8d8;
+ padding-bottom: 4px;
+ text-transform: capitalize;
}
&__activities-container {
@@ -21,8 +22,8 @@
left: 0;
top: 0;
height: 100%;
- width: 6px;
- border-right: 1px solid $scorpion;
+ width: 7px;
+ border-right: 1px solid #909090;
}
&:first-child::after {
@@ -40,22 +41,25 @@
}
&__activity-icon {
- width: 13px;
- height: 13px;
+ width: 15px;
+ height: 15px;
margin-right: 6px;
border-radius: 50%;
- background: $scorpion;
+ background: #909090;
flex: 0 0 auto;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1;
}
&__activity-text {
- color: $scorpion;
+ color: $dusty-gray;
font-size: .75rem;
+ cursor: pointer;
- @media screen and (min-width: $break-large) {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ &:hover {
+ color: $black;
}
}
@@ -64,6 +68,16 @@
font-weight: 500;
}
+ &__entry-container {
+ min-width: 0;
+ }
+
+ &__action-link {
+ font-size: .75rem;
+ cursor: pointer;
+ color: $curious-blue;
+ }
+
b {
font-weight: 500;
}
diff --git a/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.component.test.js b/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.component.test.js
new file mode 100644
index 000000000..a2946e53d
--- /dev/null
+++ b/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.component.test.js
@@ -0,0 +1,101 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import TransactionActivityLog from '../transaction-activity-log.component'
+
+describe('TransactionActivityLog Component', () => {
+ it('should render properly', () => {
+ const activities = [
+ {
+ eventKey: 'transactionCreated',
+ hash: '0xe46c7f9b39af2fbf1c53e66f72f80343ab54c2c6dba902d51fb98ada08fe1a63',
+ id: 2005383477493174,
+ timestamp: 1543957986150,
+ value: '0x2386f26fc10000',
+ }, {
+ eventKey: 'transactionSubmitted',
+ hash: '0xe46c7f9b39af2fbf1c53e66f72f80343ab54c2c6dba902d51fb98ada08fe1a63',
+ id: 2005383477493174,
+ timestamp: 1543957987853,
+ value: '0x1319718a5000',
+ }, {
+ eventKey: 'transactionResubmitted',
+ hash: '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87',
+ id: 2005383477493175,
+ timestamp: 1543957991563,
+ value: '0x1502634b5800',
+ }, {
+ eventKey: 'transactionConfirmed',
+ hash: '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87',
+ id: 2005383477493175,
+ timestamp: 1543958029960,
+ value: '0x1502634b5800',
+ },
+ ]
+
+ const wrapper = shallow(
+ <TransactionActivityLog
+ activities={activities}
+ className="test-class"
+ inlineRetryIndex={-1}
+ inlineCancelIndex={-1}
+ nativeCurrency="ETH"
+ onCancel={() => {}}
+ onRetry={() => {}}
+ primaryTransactionStatus="confirmed"
+ />,
+ { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }
+ )
+
+ assert.ok(wrapper.hasClass('transaction-activity-log'))
+ assert.ok(wrapper.hasClass('test-class'))
+ })
+
+ it('should render inline retry and cancel buttons', () => {
+ const activities = [
+ {
+ eventKey: 'transactionCreated',
+ hash: '0xa',
+ id: 1,
+ timestamp: 1,
+ value: '0x1',
+ }, {
+ eventKey: 'transactionSubmitted',
+ hash: '0xa',
+ id: 1,
+ timestamp: 2,
+ value: '0x1',
+ }, {
+ eventKey: 'transactionResubmitted',
+ hash: '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87',
+ id: 2,
+ timestamp: 3,
+ value: '0x1',
+ }, {
+ eventKey: 'transactionCancelAttempted',
+ hash: '0x7d09d337fc6f5d6fe2dbf3a6988d69532deb0a82b665f9180b5a20db377eea87',
+ id: 3,
+ timestamp: 4,
+ value: '0x1',
+ },
+ ]
+
+ const wrapper = shallow(
+ <TransactionActivityLog
+ activities={activities}
+ className="test-class"
+ inlineRetryIndex={2}
+ inlineCancelIndex={3}
+ nativeCurrency="ETH"
+ onCancel={() => {}}
+ onRetry={() => {}}
+ primaryTransactionStatus="pending"
+ />,
+ { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }
+ )
+
+ assert.ok(wrapper.hasClass('transaction-activity-log'))
+ assert.ok(wrapper.hasClass('test-class'))
+ assert.equal(wrapper.find('.transaction-activity-log__action-link').length, 2)
+ })
+})
diff --git a/ui/app/components/transaction-activity-log/tests/transaction-activity-log.container.test.js b/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.container.test.js
index a7c35f51e..a7c35f51e 100644
--- a/ui/app/components/transaction-activity-log/tests/transaction-activity-log.container.test.js
+++ b/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.container.test.js
diff --git a/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.util.test.js b/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.util.test.js
new file mode 100644
index 000000000..d014b8886
--- /dev/null
+++ b/ui/app/components/app/transaction-activity-log/tests/transaction-activity-log.util.test.js
@@ -0,0 +1,335 @@
+import assert from 'assert'
+import { combineTransactionHistories, getActivities } from '../transaction-activity-log.util'
+
+describe('combineTransactionHistories', () => {
+ it('should return no activites for an empty list of transactions', () => {
+ assert.deepEqual(combineTransactionHistories([]), [])
+ })
+
+ it('should return activities for an array of transactions', () => {
+ const transactions = [
+ {
+ estimatedGas: '0x5208',
+ hash: '0xa14f13d36b3901e352ce3a7acb9b47b001e5a3370f06232a0953c6fc6fad91b3',
+ history: [
+ {
+ 'id': 6400627574331058,
+ 'time': 1543958845581,
+ 'status': 'unapproved',
+ 'metamaskNetworkId': '3',
+ 'loadingDefaults': true,
+ 'txParams': {
+ 'from': '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706',
+ 'to': '0xc5ae6383e126f901dcb06131d97a88745bfa88d6',
+ 'value': '0x2386f26fc10000',
+ 'gas': '0x5208',
+ 'gasPrice': '0x3b9aca00',
+ },
+ 'type': 'standard',
+ },
+ [{ 'op': 'replace', 'path': '/status', 'value': 'approved', 'note': 'txStateManager: setting status to approved', 'timestamp': 1543958847813 }],
+ [{ 'op': 'replace', 'path': '/status', 'value': 'submitted', 'note': 'txStateManager: setting status to submitted', 'timestamp': 1543958848147 }],
+ [{ 'op': 'replace', 'path': '/status', 'value': 'dropped', 'note': 'txStateManager: setting status to dropped', 'timestamp': 1543958897181 }, { 'op': 'add', 'path': '/replacedBy', 'value': '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33' }],
+ ],
+ id: 6400627574331058,
+ loadingDefaults: false,
+ metamaskNetworkId: '3',
+ status: 'dropped',
+ submittedTime: 1543958848135,
+ time: 1543958845581,
+ txParams: {
+ from: '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706',
+ gas: '0x5208',
+ gasPrice: '0x3b9aca00',
+ nonce: '0x32',
+ to: '0xc5ae6383e126f901dcb06131d97a88745bfa88d6',
+ value: '0x2386f26fc10000',
+ },
+ type: 'standard',
+ }, {
+ hash: '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33',
+ history: [
+ {
+ 'id': 6400627574331060,
+ 'time': 1543958857697,
+ 'status': 'unapproved',
+ 'metamaskNetworkId': '3',
+ 'loadingDefaults': false,
+ 'txParams': {
+ 'from': '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706',
+ 'to': '0xc5ae6383e126f901dcb06131d97a88745bfa88d6',
+ 'value': '0x2386f26fc10000',
+ 'gas': '0x5208',
+ 'gasPrice': '0x3b9aca00',
+ 'nonce': '0x32',
+ },
+ 'lastGasPrice': '0x4190ab00',
+ 'type': 'retry',
+ },
+ [{ 'op': 'replace', 'path': '/txParams/gasPrice', 'value': '0x481f2280', 'note': 'confTx: user approved transaction', 'timestamp': 1543958859470 }],
+ [{ 'op': 'replace', 'path': '/status', 'value': 'approved', 'note': 'txStateManager: setting status to approved', 'timestamp': 1543958859485 }],
+ [{ 'op': 'replace', 'path': '/status', 'value': 'signed', 'note': 'transactions#publishTransaction', 'timestamp': 1543958859889 }],
+ [{ 'op': 'replace', 'path': '/status', 'value': 'submitted', 'note': 'txStateManager: setting status to submitted', 'timestamp': 1543958860061 }], [{ 'op': 'add', 'path': '/firstRetryBlockNumber', 'value': '0x45a0fd', 'note': 'transactions/pending-tx-tracker#event: tx:block-update', 'timestamp': 1543958896466 }],
+ [{ 'op': 'replace', 'path': '/status', 'value': 'confirmed', 'timestamp': 1543958897165 }],
+ ],
+ id: 6400627574331060,
+ lastGasPrice: '0x4190ab00',
+ loadingDefaults: false,
+ metamaskNetworkId: '3',
+ status: 'confirmed',
+ submittedTime: 1543958860054,
+ time: 1543958857697,
+ txParams: {
+ from: '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706',
+ gas: '0x5208',
+ gasPrice: '0x481f2280',
+ nonce: '0x32',
+ to: '0xc5ae6383e126f901dcb06131d97a88745bfa88d6',
+ value: '0x2386f26fc10000',
+ },
+ txReceipt: {
+ status: '0x1',
+ },
+ type: 'retry',
+ },
+ ]
+
+ const expected = [
+ {
+ id: 6400627574331058,
+ hash: '0xa14f13d36b3901e352ce3a7acb9b47b001e5a3370f06232a0953c6fc6fad91b3',
+ eventKey: 'transactionCreated',
+ timestamp: 1543958845581,
+ value: '0x2386f26fc10000',
+ }, {
+ id: 6400627574331058,
+ hash: '0xa14f13d36b3901e352ce3a7acb9b47b001e5a3370f06232a0953c6fc6fad91b3',
+ eventKey: 'transactionSubmitted',
+ timestamp: 1543958848147,
+ value: '0x1319718a5000',
+ }, {
+ id: 6400627574331060,
+ hash: '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33',
+ eventKey: 'transactionResubmitted',
+ timestamp: 1543958860061,
+ value: '0x171c3a061400',
+ }, {
+ id: 6400627574331060,
+ hash: '0xecbe181ee67c4291d04a7cb9ffbf1d5d831e4fbaa89994fd06bab5dd4cc79b33',
+ eventKey: 'transactionConfirmed',
+ timestamp: 1543958897165,
+ value: '0x171c3a061400',
+ },
+ ]
+
+ assert.deepEqual(combineTransactionHistories(transactions), expected)
+ })
+})
+
+describe('getActivities', () => {
+ it('should return no activities for an empty history', () => {
+ const transaction = {
+ history: [],
+ id: 1,
+ status: 'confirmed',
+ txParams: {
+ from: '0x1',
+ gas: '0x5208',
+ gasPrice: '0x3b9aca00',
+ nonce: '0xa4',
+ to: '0x2',
+ value: '0x2386f26fc10000',
+ },
+ }
+
+ assert.deepEqual(getActivities(transaction), [])
+ })
+
+ it('should return activities for a transaction\'s history', () => {
+ const transaction = {
+ history: [
+ {
+ id: 5559712943815343,
+ loadingDefaults: true,
+ metamaskNetworkId: '3',
+ status: 'unapproved',
+ time: 1535507561452,
+ txParams: {
+ from: '0x1',
+ gas: '0x5208',
+ gasPrice: '0x3b9aca00',
+ nonce: '0xa4',
+ to: '0x2',
+ value: '0x2386f26fc10000',
+ },
+ },
+ [
+ {
+ op: 'replace',
+ path: '/loadingDefaults',
+ timestamp: 1535507561515,
+ value: false,
+ },
+ {
+ op: 'add',
+ path: '/gasPriceSpecified',
+ value: true,
+ },
+ {
+ op: 'add',
+ path: '/gasLimitSpecified',
+ value: true,
+ },
+ {
+ op: 'add',
+ path: '/estimatedGas',
+ value: '0x5208',
+ },
+ ],
+ [
+ {
+ note: '#newUnapprovedTransaction - adding the origin',
+ op: 'add',
+ path: '/origin',
+ timestamp: 1535507561516,
+ value: 'MetaMask',
+ },
+ [],
+ ],
+ [
+ {
+ note: 'confTx: user approved transaction',
+ op: 'replace',
+ path: '/txParams/gasPrice',
+ timestamp: 1535664571504,
+ value: '0x77359400',
+ },
+ ],
+ [
+ {
+ note: 'txStateManager: setting status to approved',
+ op: 'replace',
+ path: '/status',
+ timestamp: 1535507564302,
+ value: 'approved',
+ },
+ ],
+ [
+ {
+ note: 'transactions#approveTransaction',
+ op: 'add',
+ path: '/txParams/nonce',
+ timestamp: 1535507564439,
+ value: '0xa4',
+ },
+ {
+ op: 'add',
+ path: '/nonceDetails',
+ value: {
+ local: {},
+ network: {},
+ params: {},
+ },
+ },
+ ],
+ [
+ {
+ note: 'transactions#publishTransaction',
+ op: 'replace',
+ path: '/status',
+ timestamp: 1535507564518,
+ value: 'signed',
+ },
+ {
+ op: 'add',
+ path: '/rawTx',
+ value: '0xf86b81a4843b9aca008252089450a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706872386f26fc10000802aa007b30119fc4fc5954fad727895b7e3ba80a78d197e95703cc603bcf017879151a01c50beda40ffaee541da9c05b9616247074f25f392800e0ad6c7a835d5366edf',
+ },
+ ],
+ [],
+ [
+ {
+ note: 'transactions#setTxHash',
+ op: 'add',
+ path: '/hash',
+ timestamp: 1535507564658,
+ value: '0x7acc4987b5c0dfa8d423798a8c561138259de1f98a62e3d52e7e83c0e0dd9fb7',
+ },
+ ],
+ [
+ {
+ note: 'txStateManager - add submitted time stamp',
+ op: 'add',
+ path: '/submittedTime',
+ timestamp: 1535507564660,
+ value: 1535507564660,
+ },
+ ],
+ [
+ {
+ note: 'txStateManager: setting status to submitted',
+ op: 'replace',
+ path: '/status',
+ timestamp: 1535507564665,
+ value: 'submitted',
+ },
+ ],
+ [
+ {
+ note: 'transactions/pending-tx-tracker#event: tx:block-update',
+ op: 'add',
+ path: '/firstRetryBlockNumber',
+ timestamp: 1535507575476,
+ value: '0x3bf624',
+ },
+ ],
+ [
+ {
+ note: 'txStateManager: setting status to confirmed',
+ op: 'replace',
+ path: '/status',
+ timestamp: 1535507615993,
+ value: 'confirmed',
+ },
+ ],
+ ],
+ id: 1,
+ status: 'confirmed',
+ txParams: {
+ from: '0x1',
+ gas: '0x5208',
+ gasPrice: '0x3b9aca00',
+ nonce: '0xa4',
+ to: '0x2',
+ value: '0x2386f26fc10000',
+ },
+ hash: '0xabc',
+ }
+
+ const expectedResult = [
+ {
+ 'eventKey': 'transactionCreated',
+ 'timestamp': 1535507561452,
+ 'value': '0x2386f26fc10000',
+ 'id': 1,
+ 'hash': '0xabc',
+ },
+ {
+ 'eventKey': 'transactionSubmitted',
+ 'timestamp': 1535507564665,
+ 'value': '0x2632e314a000',
+ 'id': 1,
+ 'hash': '0xabc',
+ },
+ {
+ 'eventKey': 'transactionConfirmed',
+ 'timestamp': 1535507615993,
+ 'value': '0x2632e314a000',
+ 'id': 1,
+ 'hash': '0xabc',
+ },
+ ]
+
+ assert.deepEqual(getActivities(transaction, true), expectedResult)
+ })
+})
diff --git a/ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/index.js b/ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/index.js
new file mode 100644
index 000000000..86b12360a
--- /dev/null
+++ b/ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/index.js
@@ -0,0 +1 @@
+export { default } from './transaction-activity-log-icon.component'
diff --git a/ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/transaction-activity-log-icon.component.js b/ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/transaction-activity-log-icon.component.js
new file mode 100644
index 000000000..871716002
--- /dev/null
+++ b/ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/transaction-activity-log-icon.component.js
@@ -0,0 +1,55 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+
+import {
+ TRANSACTION_CREATED_EVENT,
+ TRANSACTION_SUBMITTED_EVENT,
+ TRANSACTION_RESUBMITTED_EVENT,
+ TRANSACTION_CONFIRMED_EVENT,
+ TRANSACTION_DROPPED_EVENT,
+ TRANSACTION_ERRORED_EVENT,
+ TRANSACTION_CANCEL_ATTEMPTED_EVENT,
+ TRANSACTION_CANCEL_SUCCESS_EVENT,
+} from '../transaction-activity-log.constants'
+
+const imageHash = {
+ [TRANSACTION_CREATED_EVENT]: '/images/icons/new.svg',
+ [TRANSACTION_SUBMITTED_EVENT]: '/images/icons/submitted.svg',
+ [TRANSACTION_RESUBMITTED_EVENT]: '/images/icons/retry.svg',
+ [TRANSACTION_CONFIRMED_EVENT]: '/images/icons/confirm.svg',
+ [TRANSACTION_DROPPED_EVENT]: '/images/icons/cancelled.svg',
+ [TRANSACTION_ERRORED_EVENT]: '/images/icons/error.svg',
+ [TRANSACTION_CANCEL_ATTEMPTED_EVENT]: '/images/icons/cancelled.svg',
+ [TRANSACTION_CANCEL_SUCCESS_EVENT]: '/images/icons/cancelled.svg',
+}
+
+export default class TransactionActivityLogIcon extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ className: PropTypes.string,
+ eventKey: PropTypes.oneOf(Object.keys(imageHash)),
+ }
+
+ render () {
+ const { className, eventKey } = this.props
+ const imagePath = imageHash[eventKey]
+
+ return (
+ <div className={classnames('transaction-activity-log-icon', className)}>
+ {
+ imagePath && (
+ <img
+ src={imagePath}
+ height={9}
+ width={9}
+ />
+ )
+ }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/app/transaction-activity-log/transaction-activity-log.component.js b/ui/app/components/app/transaction-activity-log/transaction-activity-log.component.js
new file mode 100644
index 000000000..de4d29750
--- /dev/null
+++ b/ui/app/components/app/transaction-activity-log/transaction-activity-log.component.js
@@ -0,0 +1,131 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import { getEthConversionFromWeiHex, getValueFromWeiHex } from '../../../helpers/utils/conversions.util'
+import { formatDate } from '../../../helpers/utils/util'
+import TransactionActivityLogIcon from './transaction-activity-log-icon'
+import { CONFIRMED_STATUS } from './transaction-activity-log.constants'
+import prefixForNetwork from '../../../../lib/etherscan-prefix-for-network'
+
+export default class TransactionActivityLog extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ metricEvent: PropTypes.func,
+ }
+
+ static propTypes = {
+ activities: PropTypes.array,
+ className: PropTypes.string,
+ conversionRate: PropTypes.number,
+ inlineRetryIndex: PropTypes.number,
+ inlineCancelIndex: PropTypes.number,
+ nativeCurrency: PropTypes.string,
+ onCancel: PropTypes.func,
+ onRetry: PropTypes.func,
+ primaryTransaction: PropTypes.object,
+ }
+
+ handleActivityClick = hash => {
+ const { primaryTransaction } = this.props
+ const { metamaskNetworkId } = primaryTransaction
+
+ const prefix = prefixForNetwork(metamaskNetworkId)
+ const etherscanUrl = `https://${prefix}etherscan.io/tx/${hash}`
+
+ global.platform.openWindow({ url: etherscanUrl })
+ }
+
+ renderInlineRetry (index, activity) {
+ const { t } = this.context
+ const { inlineRetryIndex, primaryTransaction = {}, onRetry } = this.props
+ const { status } = primaryTransaction
+ const { id } = activity
+
+ return status !== CONFIRMED_STATUS && index === inlineRetryIndex
+ ? (
+ <div
+ className="transaction-activity-log__action-link"
+ onClick={() => onRetry(id)}
+ >
+ { t('speedUpTransaction') }
+ </div>
+ ) : null
+ }
+
+ renderInlineCancel (index, activity) {
+ const { t } = this.context
+ const { inlineCancelIndex, primaryTransaction = {}, onCancel } = this.props
+ const { status } = primaryTransaction
+ const { id } = activity
+
+ return status !== CONFIRMED_STATUS && index === inlineCancelIndex
+ ? (
+ <div
+ className="transaction-activity-log__action-link"
+ onClick={() => onCancel(id)}
+ >
+ { t('speedUpCancellation') }
+ </div>
+ ) : null
+ }
+
+ renderActivity (activity, index) {
+ const { conversionRate, nativeCurrency } = this.props
+ const { eventKey, value, timestamp, hash } = activity
+ const ethValue = index === 0
+ ? `${getValueFromWeiHex({
+ value,
+ fromCurrency: nativeCurrency,
+ toCurrency: nativeCurrency,
+ conversionRate,
+ numberOfDecimals: 6,
+ })} ${nativeCurrency}`
+ : getEthConversionFromWeiHex({
+ value,
+ fromCurrency: nativeCurrency,
+ conversionRate,
+ numberOfDecimals: 3,
+ })
+ const formattedTimestamp = formatDate(timestamp, 'T \'on\' M/d/y')
+ const activityText = this.context.t(eventKey, [ethValue, formattedTimestamp])
+
+ return (
+ <div
+ key={index}
+ className="transaction-activity-log__activity"
+ >
+ <TransactionActivityLogIcon
+ className="transaction-activity-log__activity-icon"
+ eventKey={eventKey}
+ />
+ <div className="transaction-activity-log__entry-container">
+ <div
+ className="transaction-activity-log__activity-text"
+ title={activityText}
+ onClick={() => this.handleActivityClick(hash)}
+ >
+ { activityText }
+ </div>
+ { this.renderInlineRetry(index, activity) }
+ { this.renderInlineCancel(index, activity) }
+ </div>
+ </div>
+ )
+ }
+
+ render () {
+ const { t } = this.context
+ const { className, activities } = this.props
+
+ return (
+ <div className={classnames('transaction-activity-log', className)}>
+ <div className="transaction-activity-log__title">
+ { t('activityLog') }
+ </div>
+ <div className="transaction-activity-log__activities-container">
+ { activities.map((activity, index) => this.renderActivity(activity, index)) }
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/app/transaction-activity-log/transaction-activity-log.constants.js b/ui/app/components/app/transaction-activity-log/transaction-activity-log.constants.js
new file mode 100644
index 000000000..72e63d85c
--- /dev/null
+++ b/ui/app/components/app/transaction-activity-log/transaction-activity-log.constants.js
@@ -0,0 +1,13 @@
+export const TRANSACTION_CREATED_EVENT = 'transactionCreated'
+export const TRANSACTION_SUBMITTED_EVENT = 'transactionSubmitted'
+export const TRANSACTION_RESUBMITTED_EVENT = 'transactionResubmitted'
+export const TRANSACTION_CONFIRMED_EVENT = 'transactionConfirmed'
+export const TRANSACTION_DROPPED_EVENT = 'transactionDropped'
+export const TRANSACTION_UPDATED_EVENT = 'transactionUpdated'
+export const TRANSACTION_ERRORED_EVENT = 'transactionErrored'
+export const TRANSACTION_CANCEL_ATTEMPTED_EVENT = 'transactionCancelAttempted'
+export const TRANSACTION_CANCEL_SUCCESS_EVENT = 'transactionCancelSuccess'
+
+export const SUBMITTED_STATUS = 'submitted'
+export const CONFIRMED_STATUS = 'confirmed'
+export const DROPPED_STATUS = 'dropped'
diff --git a/ui/app/components/app/transaction-activity-log/transaction-activity-log.container.js b/ui/app/components/app/transaction-activity-log/transaction-activity-log.container.js
new file mode 100644
index 000000000..11b20f245
--- /dev/null
+++ b/ui/app/components/app/transaction-activity-log/transaction-activity-log.container.js
@@ -0,0 +1,44 @@
+import { connect } from 'react-redux'
+import R from 'ramda'
+import TransactionActivityLog from './transaction-activity-log.component'
+import { conversionRateSelector, getNativeCurrency } from '../../../selectors/selectors'
+import { combineTransactionHistories } from './transaction-activity-log.util'
+import {
+ TRANSACTION_RESUBMITTED_EVENT,
+ TRANSACTION_CANCEL_ATTEMPTED_EVENT,
+} from './transaction-activity-log.constants'
+
+const matchesEventKey = matchEventKey => ({ eventKey }) => eventKey === matchEventKey
+
+const mapStateToProps = state => {
+ return {
+ conversionRate: conversionRateSelector(state),
+ nativeCurrency: getNativeCurrency(state),
+ }
+}
+
+const mergeProps = (stateProps, dispatchProps, ownProps) => {
+ const {
+ transactionGroup: {
+ transactions = [],
+ primaryTransaction,
+ } = {},
+ ...restOwnProps
+ } = ownProps
+
+ const activities = combineTransactionHistories(transactions)
+ const inlineRetryIndex = R.findLastIndex(matchesEventKey(TRANSACTION_RESUBMITTED_EVENT))(activities)
+ const inlineCancelIndex = R.findLastIndex(matchesEventKey(TRANSACTION_CANCEL_ATTEMPTED_EVENT))(activities)
+
+ return {
+ ...stateProps,
+ ...dispatchProps,
+ ...restOwnProps,
+ activities,
+ inlineRetryIndex,
+ inlineCancelIndex,
+ primaryTransaction,
+ }
+}
+
+export default connect(mapStateToProps, null, mergeProps)(TransactionActivityLog)
diff --git a/ui/app/components/app/transaction-activity-log/transaction-activity-log.util.js b/ui/app/components/app/transaction-activity-log/transaction-activity-log.util.js
new file mode 100644
index 000000000..b74513879
--- /dev/null
+++ b/ui/app/components/app/transaction-activity-log/transaction-activity-log.util.js
@@ -0,0 +1,233 @@
+import { getHexGasTotal } from '../../../helpers/utils/confirm-tx.util'
+
+// path constants
+const STATUS_PATH = '/status'
+const GAS_PRICE_PATH = '/txParams/gasPrice'
+const GAS_LIMIT_PATH = '/txParams/gas'
+
+// op constants
+const REPLACE_OP = 'replace'
+
+import {
+ // event constants
+ TRANSACTION_CREATED_EVENT,
+ TRANSACTION_SUBMITTED_EVENT,
+ TRANSACTION_RESUBMITTED_EVENT,
+ TRANSACTION_CONFIRMED_EVENT,
+ TRANSACTION_DROPPED_EVENT,
+ TRANSACTION_UPDATED_EVENT,
+ TRANSACTION_ERRORED_EVENT,
+ TRANSACTION_CANCEL_ATTEMPTED_EVENT,
+ TRANSACTION_CANCEL_SUCCESS_EVENT,
+ // status constants
+ SUBMITTED_STATUS,
+ CONFIRMED_STATUS,
+ DROPPED_STATUS,
+} from './transaction-activity-log.constants'
+
+import {
+ TRANSACTION_TYPE_CANCEL,
+ TRANSACTION_TYPE_RETRY,
+} from '../../../../../app/scripts/controllers/transactions/enums'
+
+const eventPathsHash = {
+ [STATUS_PATH]: true,
+ [GAS_PRICE_PATH]: true,
+ [GAS_LIMIT_PATH]: true,
+}
+
+const statusHash = {
+ [SUBMITTED_STATUS]: TRANSACTION_SUBMITTED_EVENT,
+ [CONFIRMED_STATUS]: TRANSACTION_CONFIRMED_EVENT,
+ [DROPPED_STATUS]: TRANSACTION_DROPPED_EVENT,
+}
+
+/**
+ * @name getActivities
+ * @param {Object} transaction - txMeta object
+ * @param {boolean} isFirstTransaction - True if the transaction is the first created transaction
+ * in the list of transactions with the same nonce. If so, we use this transaction to create the
+ * transactionCreated activity.
+ * @returns {Array}
+ */
+export function getActivities (transaction, isFirstTransaction = false) {
+ const {
+ id,
+ hash,
+ history = [],
+ txParams: { gas: paramsGasLimit, gasPrice: paramsGasPrice},
+ xReceipt: { status } = {},
+ type,
+ } = transaction
+
+ let cachedGasLimit = '0x0'
+ let cachedGasPrice = '0x0'
+
+ const historyActivities = history.reduce((acc, base, index) => {
+ // First history item should be transaction creation
+ if (index === 0 && !Array.isArray(base) && base.txParams) {
+ const { time: timestamp, txParams: { value, gas = '0x0', gasPrice = '0x0' } = {} } = base
+ // The cached gas limit and gas price are used to display the gas fee in the activity log. We
+ // need to cache these values because the status update history events don't provide us with
+ // the latest gas limit and gas price.
+ cachedGasLimit = gas
+ cachedGasPrice = gasPrice
+
+ if (isFirstTransaction) {
+ return acc.concat({
+ id,
+ hash,
+ eventKey: TRANSACTION_CREATED_EVENT,
+ timestamp,
+ value,
+ })
+ }
+ // An entry in the history may be an array of more sub-entries.
+ } else if (Array.isArray(base)) {
+ const events = []
+
+ base.forEach(entry => {
+ const { op, path, value, timestamp: entryTimestamp } = entry
+ // Not all sub-entries in a history entry have a timestamp. If the sub-entry does not have a
+ // timestamp, the first sub-entry in a history entry should.
+ const timestamp = entryTimestamp || base[0] && base[0].timestamp
+
+ if (path in eventPathsHash && op === REPLACE_OP) {
+ switch (path) {
+ case STATUS_PATH: {
+ const gasFee = cachedGasLimit === '0x0' && cachedGasPrice === '0x0'
+ ? getHexGasTotal({ gasLimit: paramsGasLimit, gasPrice: paramsGasPrice })
+ : getHexGasTotal({ gasLimit: cachedGasLimit, gasPrice: cachedGasPrice })
+
+ if (value in statusHash) {
+ let eventKey = statusHash[value]
+
+ // If the status is 'submitted', we need to determine whether the event is a
+ // transaction retry or a cancellation attempt.
+ if (value === SUBMITTED_STATUS) {
+ if (type === TRANSACTION_TYPE_RETRY) {
+ eventKey = TRANSACTION_RESUBMITTED_EVENT
+ } else if (type === TRANSACTION_TYPE_CANCEL) {
+ eventKey = TRANSACTION_CANCEL_ATTEMPTED_EVENT
+ }
+ } else if (value === CONFIRMED_STATUS) {
+ if (type === TRANSACTION_TYPE_CANCEL) {
+ eventKey = TRANSACTION_CANCEL_SUCCESS_EVENT
+ }
+ }
+
+ events.push({
+ id,
+ hash,
+ eventKey,
+ timestamp,
+ value: gasFee,
+ })
+ }
+
+ break
+ }
+
+ // If the gas price or gas limit has been changed, we update the gasFee of the
+ // previously submitted event. These events happen when the gas limit and gas price is
+ // changed at the confirm screen.
+ case GAS_PRICE_PATH:
+ case GAS_LIMIT_PATH: {
+ const lastEvent = events[events.length - 1] || {}
+ const { lastEventKey } = lastEvent
+
+ if (path === GAS_LIMIT_PATH) {
+ cachedGasLimit = value
+ } else if (path === GAS_PRICE_PATH) {
+ cachedGasPrice = value
+ }
+
+ if (lastEventKey === TRANSACTION_SUBMITTED_EVENT ||
+ lastEventKey === TRANSACTION_RESUBMITTED_EVENT) {
+ lastEvent.value = getHexGasTotal({
+ gasLimit: cachedGasLimit,
+ gasPrice: cachedGasPrice,
+ })
+ }
+
+ break
+ }
+
+ default: {
+ events.push({
+ id,
+ hash,
+ eventKey: TRANSACTION_UPDATED_EVENT,
+ timestamp,
+ })
+ }
+ }
+ }
+ })
+
+ return acc.concat(events)
+ }
+
+ return acc
+ }, [])
+
+ // If txReceipt.status is '0x0', that means that an on-chain error occured for the transaction,
+ // so we add an error entry to the Activity Log.
+ return status === '0x0'
+ ? historyActivities.concat({ id, hash, eventKey: TRANSACTION_ERRORED_EVENT })
+ : historyActivities
+}
+
+/**
+ * @description Removes "Transaction dropped" activities from a list of sorted activities if one of
+ * the transactions has been confirmed. Typically, if multiple transactions have the same nonce,
+ * once one transaction is confirmed, the rest are dropped. In this case, we don't want to show
+ * multiple "Transaction dropped" activities, and instead want to show a single "Transaction
+ * confirmed".
+ * @param {Array} activities - List of sorted activities generated from the getActivities function.
+ * @returns {Array}
+ */
+function filterSortedActivities (activities) {
+ const filteredActivities = []
+ const hasConfirmedActivity = Boolean(activities.find(({ eventKey }) => (
+ eventKey === TRANSACTION_CONFIRMED_EVENT || eventKey === TRANSACTION_CANCEL_SUCCESS_EVENT
+ )))
+ let addedDroppedActivity = false
+
+ activities.forEach(activity => {
+ if (activity.eventKey === TRANSACTION_DROPPED_EVENT) {
+ if (!hasConfirmedActivity && !addedDroppedActivity) {
+ filteredActivities.push(activity)
+ addedDroppedActivity = true
+ }
+ } else {
+ filteredActivities.push(activity)
+ }
+ })
+
+ return filteredActivities
+}
+
+/**
+ * Combines the histories of an array of transactions into a single array.
+ * @param {Array} transactions - Array of txMeta transaction objects.
+ * @returns {Array}
+ */
+export function combineTransactionHistories (transactions = []) {
+ if (!transactions.length) {
+ return []
+ }
+
+ const activities = []
+
+ transactions.forEach((transaction, index) => {
+ // The first transaction should be the transaction with the earliest submittedTime. We show the
+ // 'created' and 'submitted' activities here. All subsequent transactions will use 'resubmitted'
+ // instead.
+ const transactionActivities = getActivities(transaction, index === 0)
+ activities.push(...transactionActivities)
+ })
+
+ const sortedActivities = activities.sort((a, b) => a.timestamp - b.timestamp)
+ return filterSortedActivities(sortedActivities)
+}
diff --git a/ui/app/components/transaction-breakdown/index.js b/ui/app/components/app/transaction-breakdown/index.js
index 4a5b52663..4a5b52663 100644
--- a/ui/app/components/transaction-breakdown/index.js
+++ b/ui/app/components/app/transaction-breakdown/index.js
diff --git a/ui/app/components/transaction-breakdown/index.scss b/ui/app/components/app/transaction-breakdown/index.scss
index 1bb108943..c8144eac2 100644
--- a/ui/app/components/transaction-breakdown/index.scss
+++ b/ui/app/components/app/transaction-breakdown/index.scss
@@ -1,9 +1,10 @@
-@import './transaction-breakdown-row/index';
+@import 'transaction-breakdown-row/index';
.transaction-breakdown {
- &__card {
- background: $white;
- height: 100%;
+ &__title {
+ border-bottom: 1px solid #d8d8d8;
+ padding-bottom: 4px;
+ text-transform: capitalize;
}
&__row-title {
diff --git a/ui/app/components/transaction-breakdown/tests/transaction-breakdown.component.test.js b/ui/app/components/app/transaction-breakdown/tests/transaction-breakdown.component.test.js
index d18cd420c..4512b84f0 100644
--- a/ui/app/components/transaction-breakdown/tests/transaction-breakdown.component.test.js
+++ b/ui/app/components/app/transaction-breakdown/tests/transaction-breakdown.component.test.js
@@ -2,8 +2,6 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import TransactionBreakdown from '../transaction-breakdown.component'
-import TransactionBreakdownRow from '../transaction-breakdown-row'
-import Card from '../../card'
describe('TransactionBreakdown Component', () => {
it('should render properly', () => {
@@ -31,7 +29,5 @@ describe('TransactionBreakdown Component', () => {
assert.ok(wrapper.hasClass('transaction-breakdown'))
assert.ok(wrapper.hasClass('test-class'))
- assert.equal(wrapper.find(Card).length, 1)
- assert.equal(wrapper.find(Card).find(TransactionBreakdownRow).length, 4)
})
})
diff --git a/ui/app/components/transaction-breakdown/transaction-breakdown-row/index.js b/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/index.js
index 557bf75fb..557bf75fb 100644
--- a/ui/app/components/transaction-breakdown/transaction-breakdown-row/index.js
+++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/index.js
diff --git a/ui/app/components/transaction-breakdown/transaction-breakdown-row/index.scss b/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/index.scss
index 8c73be1a6..8c73be1a6 100644
--- a/ui/app/components/transaction-breakdown/transaction-breakdown-row/index.scss
+++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/index.scss
diff --git a/ui/app/components/transaction-breakdown/transaction-breakdown-row/tests/transaction-breakdown-row.component.test.js b/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/tests/transaction-breakdown-row.component.test.js
index c19399dbb..82e40fce2 100644
--- a/ui/app/components/transaction-breakdown/transaction-breakdown-row/tests/transaction-breakdown-row.component.test.js
+++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/tests/transaction-breakdown-row.component.test.js
@@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import TransactionBreakdownRow from '../transaction-breakdown-row.component'
-import Button from '../../../button'
+import Button from '../../../../ui/button'
describe('TransactionBreakdownRow Component', () => {
it('should render text properly', () => {
diff --git a/ui/app/components/transaction-breakdown/transaction-breakdown-row/transaction-breakdown-row.component.js b/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/transaction-breakdown-row.component.js
index c11ff8efa..c11ff8efa 100644
--- a/ui/app/components/transaction-breakdown/transaction-breakdown-row/transaction-breakdown-row.component.js
+++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown-row/transaction-breakdown-row.component.js
diff --git a/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js b/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js
new file mode 100644
index 000000000..5642e0fa5
--- /dev/null
+++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js
@@ -0,0 +1,106 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import TransactionBreakdownRow from './transaction-breakdown-row'
+import CurrencyDisplay from '../../ui/currency-display'
+import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
+import HexToDecimal from '../../ui/hex-to-decimal'
+import { GWEI, PRIMARY, SECONDARY } from '../../../helpers/constants/common'
+
+export default class TransactionBreakdown extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ transaction: PropTypes.object,
+ className: PropTypes.string,
+ nativeCurrency: PropTypes.string.isRequired,
+ showFiat: PropTypes.bool,
+ gas: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ gasPrice: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ gasUsed: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ totalInHex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ }
+
+ static defaultProps = {
+ transaction: {},
+ showFiat: true,
+ }
+
+ render () {
+ const { t } = this.context
+ const { gas, gasPrice, value, className, nativeCurrency, showFiat, totalInHex, gasUsed } = this.props
+
+ return (
+ <div className={classnames('transaction-breakdown', className)}>
+ <div className="transaction-breakdown__title">
+ { t('transaction') }
+ </div>
+ <TransactionBreakdownRow title={t('amount')}>
+ <UserPreferencedCurrencyDisplay
+ className="transaction-breakdown__value"
+ type={PRIMARY}
+ value={value}
+ />
+ </TransactionBreakdownRow>
+ <TransactionBreakdownRow
+ title={`${t('gasLimit')} (${t('units')})`}
+ className="transaction-breakdown__row-title"
+ >
+ {typeof gas !== 'undefined'
+ ? <HexToDecimal
+ className="transaction-breakdown__value"
+ value={gas}
+ />
+ : '?'
+ }
+ </TransactionBreakdownRow>
+ {
+ typeof gasUsed === 'string' && (
+ <TransactionBreakdownRow
+ title={`${t('gasUsed')} (${t('units')})`}
+ className="transaction-breakdown__row-title"
+ >
+ <HexToDecimal
+ className="transaction-breakdown__value"
+ value={gasUsed}
+ />
+ </TransactionBreakdownRow>
+ )
+ }
+ <TransactionBreakdownRow title={t('gasPrice')}>
+ {typeof gasPrice !== 'undefined'
+ ? <CurrencyDisplay
+ className="transaction-breakdown__value"
+ currency={nativeCurrency}
+ denomination={GWEI}
+ value={gasPrice}
+ hideLabel
+ />
+ : '?'
+ }
+ </TransactionBreakdownRow>
+ <TransactionBreakdownRow title={t('total')}>
+ <div>
+ <UserPreferencedCurrencyDisplay
+ className="transaction-breakdown__value transaction-breakdown__value--eth-total"
+ type={PRIMARY}
+ value={totalInHex}
+ />
+ {
+ showFiat && (
+ <UserPreferencedCurrencyDisplay
+ className="transaction-breakdown__value"
+ type={SECONDARY}
+ value={totalInHex}
+ />
+ )
+ }
+ </div>
+ </TransactionBreakdownRow>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/app/transaction-breakdown/transaction-breakdown.container.js b/ui/app/components/app/transaction-breakdown/transaction-breakdown.container.js
new file mode 100644
index 000000000..82f377358
--- /dev/null
+++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown.container.js
@@ -0,0 +1,29 @@
+import { connect } from 'react-redux'
+import TransactionBreakdown from './transaction-breakdown.component'
+import {getIsMainnet, getNativeCurrency, preferencesSelector} from '../../../selectors/selectors'
+import { getHexGasTotal } from '../../../helpers/utils/confirm-tx.util'
+import { sumHexes } from '../../../helpers/utils/transactions.util'
+
+const mapStateToProps = (state, ownProps) => {
+ const { transaction } = ownProps
+ const { txParams: { gas, gasPrice, value } = {}, txReceipt: { gasUsed } = {} } = transaction
+ const { showFiatInTestnets } = preferencesSelector(state)
+ const isMainnet = getIsMainnet(state)
+
+ const gasLimit = typeof gasUsed === 'string' ? gasUsed : gas
+
+ const hexGasTotal = gasLimit && gasPrice && getHexGasTotal({ gasLimit, gasPrice }) || '0x0'
+ const totalInHex = sumHexes(hexGasTotal, value)
+
+ return {
+ nativeCurrency: getNativeCurrency(state),
+ showFiat: (isMainnet || !!showFiatInTestnets),
+ totalInHex,
+ gas,
+ gasPrice,
+ value,
+ gasUsed,
+ }
+}
+
+export default connect(mapStateToProps)(TransactionBreakdown)
diff --git a/ui/app/components/transaction-list-item-details/index.js b/ui/app/components/app/transaction-list-item-details/index.js
index 0e878d032..0e878d032 100644
--- a/ui/app/components/transaction-list-item-details/index.js
+++ b/ui/app/components/app/transaction-list-item-details/index.js
diff --git a/ui/app/components/transaction-list-item-details/index.scss b/ui/app/components/app/transaction-list-item-details/index.scss
index 54cf834cc..7cb253e4e 100644
--- a/ui/app/components/transaction-list-item-details/index.scss
+++ b/ui/app/components/app/transaction-list-item-details/index.scss
@@ -1,11 +1,16 @@
.transaction-list-item-details {
&__header {
- margin-bottom: 8px;
+ margin: 8px 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
+ &__body {
+ background: #fafbfc;
+ padding: 8px 16px;
+ }
+
&__header-buttons {
display: flex;
flex-direction: row;
@@ -17,6 +22,11 @@
&:not(:last-child) {
margin-right: 8px;
}
+
+ &__copy-icon {
+ width: 10px;
+ height: 10px;
+ }
}
&__sender-to-recipient-container {
@@ -45,5 +55,9 @@
&__transaction-activity-log {
flex: 2;
min-width: 0;
+
+ @media screen and (min-width: $break-large) {
+ padding-left: 12px;
+ }
}
}
diff --git a/ui/app/components/transaction-list-item-details/tests/transaction-list-item-details.component.test.js b/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js
index f2bbe8789..c4e118b01 100644
--- a/ui/app/components/transaction-list-item-details/tests/transaction-list-item-details.component.test.js
+++ b/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js
@@ -2,8 +2,8 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import TransactionListItemDetails from '../transaction-list-item-details.component'
-import Button from '../../button'
-import SenderToRecipient from '../../sender-to-recipient'
+import Button from '../../../ui/button'
+import SenderToRecipient from '../../../ui/sender-to-recipient'
import TransactionBreakdown from '../../transaction-breakdown'
import TransactionActivityLog from '../../transaction-activity-log'
@@ -23,15 +23,21 @@ describe('TransactionListItemDetails Component', () => {
},
}
+ const transactionGroup = {
+ transactions: [transaction],
+ primaryTransaction: transaction,
+ initialTransaction: transaction,
+ }
+
const wrapper = shallow(
<TransactionListItemDetails
- transaction={transaction}
+ transactionGroup={transactionGroup}
/>,
{ context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }
)
assert.ok(wrapper.hasClass('transaction-list-item-details'))
- assert.equal(wrapper.find(Button).length, 1)
+ assert.equal(wrapper.find(Button).length, 2)
assert.equal(wrapper.find(SenderToRecipient).length, 1)
assert.equal(wrapper.find(TransactionBreakdown).length, 1)
assert.equal(wrapper.find(TransactionActivityLog).length, 1)
@@ -52,15 +58,24 @@ describe('TransactionListItemDetails Component', () => {
},
}
+ const transactionGroup = {
+ transactions: [transaction],
+ primaryTransaction: transaction,
+ initialTransaction: transaction,
+ nonce: '0xa4',
+ hasRetried: false,
+ hasCancelled: false,
+ }
+
const wrapper = shallow(
<TransactionListItemDetails
- transaction={transaction}
+ transactionGroup={transactionGroup}
showRetry={true}
/>,
{ context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }
)
assert.ok(wrapper.hasClass('transaction-list-item-details'))
- assert.equal(wrapper.find(Button).length, 2)
+ assert.equal(wrapper.find(Button).length, 3)
})
})
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
new file mode 100644
index 000000000..4a3b04998
--- /dev/null
+++ b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js
@@ -0,0 +1,215 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import copyToClipboard from 'copy-to-clipboard'
+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 = {
+ t: PropTypes.func,
+ metricsEvent: PropTypes.func,
+ }
+
+ static propTypes = {
+ onCancel: PropTypes.func,
+ onRetry: PropTypes.func,
+ showCancel: PropTypes.bool,
+ showRetry: PropTypes.bool,
+ cancelDisabled: PropTypes.bool,
+ transactionGroup: PropTypes.object,
+ }
+
+ state = {
+ justCopied: false,
+ cancelDisabled: false,
+ }
+
+ handleEtherscanClick = () => {
+ const { transactionGroup: { primaryTransaction } } = this.props
+ const { hash, metamaskNetworkId } = primaryTransaction
+
+ const prefix = prefixForNetwork(metamaskNetworkId)
+ const etherscanUrl = `https://${prefix}etherscan.io/tx/${hash}`
+
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Activity Log',
+ name: 'Clicked "View on Etherscan"',
+ },
+ })
+
+ global.platform.openWindow({ url: etherscanUrl })
+ }
+
+ handleCancel = event => {
+ const { transactionGroup: { initialTransaction: { id } = {} } = {}, onCancel } = this.props
+
+ event.stopPropagation()
+ onCancel(id)
+ }
+
+ handleRetry = event => {
+ const { transactionGroup: { initialTransaction: { id } = {} } = {}, onRetry } = this.props
+
+ event.stopPropagation()
+ onRetry(id)
+ }
+
+ handleCopyTxId = () => {
+ const { transactionGroup} = this.props
+ const { primaryTransaction: transaction } = transactionGroup
+ const { hash } = transaction
+
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Activity Log',
+ name: 'Copied Transaction ID',
+ },
+ })
+
+ this.setState({ justCopied: true }, () => {
+ copyToClipboard(hash)
+ setTimeout(() => this.setState({ justCopied: false }), 1000)
+ })
+ }
+
+ renderCancel () {
+ const { t } = this.context
+ const {
+ showCancel,
+ cancelDisabled,
+ } = this.props
+
+ if (!showCancel) {
+ return null
+ }
+
+ return cancelDisabled
+ ? (
+ <Tooltip title={t('notEnoughGas')}>
+ <div>
+ <Button
+ type="raised"
+ onClick={this.handleCancel}
+ className="transaction-list-item-details__header-button"
+ disabled
+ >
+ { t('cancel') }
+ </Button>
+ </div>
+ </Tooltip>
+ )
+ : (
+ <Button
+ type="raised"
+ onClick={this.handleCancel}
+ className="transaction-list-item-details__header-button"
+ >
+ { t('cancel') }
+ </Button>
+ )
+ }
+
+ render () {
+ const { t } = this.context
+ const { justCopied } = this.state
+ const {
+ transactionGroup,
+ showRetry,
+ onCancel,
+ onRetry,
+ } = this.props
+ const { primaryTransaction: transaction } = transactionGroup
+ const { txParams: { to, from } = {} } = transaction
+
+ return (
+ <div className="transaction-list-item-details">
+ <div className="transaction-list-item-details__header">
+ <div>{ t('details') }</div>
+ <div className="transaction-list-item-details__header-buttons">
+ {
+ showRetry && (
+ <Button
+ type="raised"
+ onClick={this.handleRetry}
+ className="transaction-list-item-details__header-button"
+ >
+ { t('speedUp') }
+ </Button>
+ )
+ }
+ { this.renderCancel() }
+ <Tooltip title={justCopied ? t('copiedTransactionId') : t('copyTransactionId')}>
+ <Button
+ type="raised"
+ onClick={this.handleCopyTxId}
+ className="transaction-list-item-details__header-button"
+ >
+ <img
+ className="transaction-list-item-details__header-button__copy-icon"
+ src="/images/copy-to-clipboard.svg"
+ />
+ </Button>
+ </Tooltip>
+ <Tooltip title={t('viewOnEtherscan')}>
+ <Button
+ type="raised"
+ onClick={this.handleEtherscanClick}
+ className="transaction-list-item-details__header-button"
+ >
+ <img src="/images/arrow-popout.svg" />
+ </Button>
+ </Tooltip>
+ </div>
+ </div>
+ <div className="transaction-list-item-details__body">
+ <div className="transaction-list-item-details__sender-to-recipient-container">
+ <SenderToRecipient
+ variant={FLAT_VARIANT}
+ addressOnly
+ recipientAddress={to}
+ senderAddress={from}
+ onRecipientClick={() => {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Activity Log',
+ name: 'Copied "To" Address',
+ },
+ })
+ }}
+ onSenderClick={() => {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Activity Log',
+ name: 'Copied "From" Address',
+ },
+ })
+ }}
+ />
+ </div>
+ <div className="transaction-list-item-details__cards-container">
+ <TransactionBreakdown
+ transaction={transaction}
+ className="transaction-list-item-details__transaction-breakdown"
+ />
+ <TransactionActivityLog
+ transactionGroup={transactionGroup}
+ className="transaction-list-item-details__transaction-activity-log"
+ onCancel={onCancel}
+ onRetry={onRetry}
+ />
+ </div>
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/transaction-list-item/index.js b/ui/app/components/app/transaction-list-item/index.js
index 697cc55e9..697cc55e9 100644
--- a/ui/app/components/transaction-list-item/index.js
+++ b/ui/app/components/app/transaction-list-item/index.js
diff --git a/ui/app/components/transaction-list-item/index.scss b/ui/app/components/app/transaction-list-item/index.scss
index ac0e7beeb..9e73a546c 100644
--- a/ui/app/components/transaction-list-item/index.scss
+++ b/ui/app/components/app/transaction-list-item/index.scss
@@ -80,6 +80,8 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
+ min-width: 0;
+ max-width: 100%;
&--primary {
text-align: end;
@@ -115,12 +117,6 @@
}
}
- &__details-container {
- padding: 8px 16px 16px;
- background: #f3f4f7;
- width: 100%;
- }
-
&__expander {
max-height: 0px;
width: 100%;
diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
index 696634fe0..c7d9dd7c7 100644
--- a/ui/app/components/transaction-list-item/transaction-list-item.component.js
+++ b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
@@ -1,16 +1,16 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-import Identicon from '../identicon'
+import Identicon from '../../ui/identicon'
import TransactionStatus from '../transaction-status'
import TransactionAction from '../transaction-action'
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
-import TokenCurrencyDisplay from '../token-currency-display'
+import TokenCurrencyDisplay from '../../ui/token-currency-display'
import TransactionListItemDetails from '../transaction-list-item-details'
-import { CONFIRM_TRANSACTION_ROUTE } from '../../routes'
-import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions'
-import { PRIMARY, SECONDARY } from '../../constants/common'
-import { getStatusKey } from '../../helpers/transactions.util'
+import { CONFIRM_TRANSACTION_ROUTE } from '../../../helpers/constants/routes'
+import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../../helpers/constants/transactions'
+import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'
+import { getStatusKey } from '../../../helpers/utils/transactions.util'
export default class TransactionListItem extends PureComponent {
static propTypes = {
@@ -18,15 +18,29 @@ export default class TransactionListItem extends PureComponent {
history: PropTypes.object,
methodData: PropTypes.object,
nonceAndDate: PropTypes.string,
+ primaryTransaction: PropTypes.object,
retryTransaction: PropTypes.func,
setSelectedToken: PropTypes.func,
showCancelModal: PropTypes.func,
showCancel: PropTypes.bool,
+ hasEnoughCancelGas: PropTypes.bool,
showRetry: PropTypes.bool,
+ showFiat: PropTypes.bool,
token: PropTypes.object,
tokenData: PropTypes.object,
transaction: PropTypes.object,
+ transactionGroup: PropTypes.object,
value: PropTypes.string,
+ fetchBasicGasAndTimeEstimates: PropTypes.func,
+ fetchGasEstimates: PropTypes.func,
+ }
+
+ static defaultProps = {
+ showFiat: true,
+ }
+
+ static contextTypes = {
+ metricsEvent: PropTypes.func,
}
state = {
@@ -46,36 +60,61 @@ export default class TransactionListItem extends PureComponent {
return
}
+ if (!showTransactionDetails) {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Home',
+ name: 'Expand Transaction',
+ },
+ })
+ }
+
this.setState({ showTransactionDetails: !showTransactionDetails })
}
- handleCancel = () => {
- const { transaction: { id, txParams: { gasPrice } } = {}, showCancelModal } = this.props
- showCancelModal(id, gasPrice)
+ handleCancel = id => {
+ const {
+ primaryTransaction: { txParams: { gasPrice } } = {},
+ transaction: { id: initialTransactionId },
+ showCancelModal,
+ } = this.props
+
+ const cancelId = id || initialTransactionId
+ showCancelModal(cancelId, gasPrice)
}
- handleRetry = () => {
+ /**
+ * @name handleRetry
+ * @description Resubmits a transaction. Retrying a transaction within a list of transactions with
+ * the same nonce requires keeping the original value while increasing the gas price of the latest
+ * transaction.
+ * @param {number} id - Transaction id
+ */
+ handleRetry = id => {
const {
- transaction: { txParams: { to } = {} },
+ primaryTransaction: { txParams: { gasPrice } } = {},
+ transaction: { txParams: { to } = {}, id: initialTransactionId },
methodData: { name } = {},
setSelectedToken,
+ retryTransaction,
+ fetchBasicGasAndTimeEstimates,
+ fetchGasEstimates,
} = this.props
if (name === TOKEN_METHOD_TRANSFER) {
setSelectedToken(to)
}
- return this.resubmit()
- }
+ const retryId = id || initialTransactionId
- resubmit () {
- const { transaction: { id }, retryTransaction, history } = this.props
- return retryTransaction(id)
- .then(id => history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`))
+ return fetchBasicGasAndTimeEstimates()
+ .then(basicEstimates => fetchGasEstimates(basicEstimates.blockTime))
+ .then(retryTransaction(retryId, gasPrice))
}
renderPrimaryCurrency () {
- const { token, transaction: { txParams: { data } = {} } = {}, value } = this.props
+ const { token, primaryTransaction: { txParams: { data } = {} } = {}, value } = this.props
return token
? (
@@ -96,9 +135,9 @@ export default class TransactionListItem extends PureComponent {
}
renderSecondaryCurrency () {
- const { token, value } = this.props
+ const { token, value, showFiat } = this.props
- return token
+ return token || !showFiat
? null
: (
<UserPreferencedCurrencyDisplay
@@ -113,12 +152,15 @@ export default class TransactionListItem extends PureComponent {
render () {
const {
assetImages,
+ transaction,
methodData,
nonceAndDate,
+ primaryTransaction,
showCancel,
+ hasEnoughCancelGas,
showRetry,
tokenData,
- transaction,
+ transactionGroup,
} = this.props
const { txParams = {} } = transaction
const { showTransactionDetails } = this.state
@@ -151,11 +193,11 @@ export default class TransactionListItem extends PureComponent {
</div>
<TransactionStatus
className="transaction-list-item__status"
- statusKey={getStatusKey(transaction)}
+ statusKey={getStatusKey(primaryTransaction)}
title={(
- (transaction.err && transaction.err.rpc)
- ? transaction.err.rpc.message
- : transaction.err && transaction.err.message
+ (primaryTransaction.err && primaryTransaction.err.rpc)
+ ? primaryTransaction.err.rpc.message
+ : primaryTransaction.err && primaryTransaction.err.message
)}
/>
{ this.renderPrimaryCurrency() }
@@ -168,11 +210,12 @@ export default class TransactionListItem extends PureComponent {
showTransactionDetails && (
<div className="transaction-list-item__details-container">
<TransactionListItemDetails
- transaction={transaction}
+ transactionGroup={transactionGroup}
onRetry={this.handleRetry}
showRetry={showRetry && methodData.done}
onCancel={this.handleCancel}
showCancel={showCancel}
+ cancelDisabled={!hasEnoughCancelGas}
/>
</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
new file mode 100644
index 000000000..de8a3bbba
--- /dev/null
+++ b/ui/app/components/app/transaction-list-item/transaction-list-item.container.js
@@ -0,0 +1,98 @@
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import { compose } from 'recompose'
+import withMethodData from '../../../helpers/higher-order-components/with-method-data'
+import TransactionListItem from './transaction-list-item.component'
+import { setSelectedToken, showModal, showSidebar, addKnownMethodData } from '../../../store/actions'
+import { hexToDecimal } from '../../../helpers/utils/conversions.util'
+import { getTokenData } from '../../../helpers/utils/transactions.util'
+import { getHexGasTotal, increaseLastGasPrice } from '../../../helpers/utils/confirm-tx.util'
+import { formatDate } from '../../../helpers/utils/util'
+import {
+ fetchBasicGasAndTimeEstimates,
+ fetchGasEstimates,
+ setCustomGasPriceForRetry,
+ setCustomGasLimit,
+} from '../../../ducks/gas/gas.duck'
+import { getIsMainnet, preferencesSelector, getSelectedAddress, conversionRateSelector } from '../../../selectors/selectors'
+import { isBalanceSufficient } from '../send/send.utils'
+
+const mapStateToProps = (state, ownProps) => {
+ const { metamask: { knownMethodData, accounts } } = 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 hasEnoughCancelGas = primaryTransaction.txParams && isBalanceSufficient({
+ amount: '0x0',
+ gasTotal: getHexGasTotal({
+ gasPrice: increaseLastGasPrice(gasPrice),
+ gasLimit,
+ }),
+ balance: selectedAccountBalance,
+ conversionRate: conversionRateSelector(state),
+ })
+
+ return {
+ knownMethodData,
+ showFiat: (isMainnet || !!showFiatInTestnets),
+ selectedAccountBalance,
+ hasEnoughCancelGas,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()),
+ fetchGasEstimates: (blockTime) => dispatch(fetchGasEstimates(blockTime)),
+ setSelectedToken: tokenAddress => dispatch(setSelectedToken(tokenAddress)),
+ addKnownMethodData: (fourBytePrefix, methodData) => dispatch(addKnownMethodData(fourBytePrefix, methodData)),
+ retryTransaction: (transaction, gasPrice) => {
+ dispatch(setCustomGasPriceForRetry(gasPrice || transaction.txParams.gasPrice))
+ dispatch(setCustomGasLimit(transaction.txParams.gas))
+ dispatch(showSidebar({
+ transitionName: 'sidebar-left',
+ type: 'customize-gas',
+ props: { transaction },
+ }))
+ },
+ showCancelModal: (transactionId, originalGasPrice) => {
+ return dispatch(showModal({ name: 'CANCEL_TRANSACTION', transactionId, originalGasPrice }))
+ },
+ }
+}
+
+const mergeProps = (stateProps, dispatchProps, ownProps) => {
+ const { transactionGroup: { primaryTransaction, initialTransaction } = {} } = ownProps
+ const { retryTransaction, ...restDispatchProps } = dispatchProps
+ const { txParams: { nonce, data } = {}, time } = initialTransaction
+ const { txParams: { value } = {} } = primaryTransaction
+
+ const tokenData = data && getTokenData(data)
+ const nonceAndDate = nonce ? `#${hexToDecimal(nonce)} - ${formatDate(time)}` : formatDate(time)
+
+ return {
+ ...stateProps,
+ ...restDispatchProps,
+ ...ownProps,
+ value,
+ nonceAndDate,
+ tokenData,
+ transaction: initialTransaction,
+ primaryTransaction,
+ retryTransaction: (transactionId, gasPrice) => {
+ const { transactionGroup: { transactions = [] } } = ownProps
+ const transaction = transactions.find(tx => tx.id === transactionId) || {}
+ const increasedGasPrice = increaseLastGasPrice(gasPrice)
+ retryTransaction(transaction, increasedGasPrice)
+ },
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps, mergeProps),
+ withMethodData,
+)(TransactionListItem)
diff --git a/ui/app/components/transaction-list/index.js b/ui/app/components/app/transaction-list/index.js
index 688994367..688994367 100644
--- a/ui/app/components/transaction-list/index.js
+++ b/ui/app/components/app/transaction-list/index.js
diff --git a/ui/app/components/transaction-list/index.scss b/ui/app/components/app/transaction-list/index.scss
index ba7ffd87b..a486f4112 100644
--- a/ui/app/components/transaction-list/index.scss
+++ b/ui/app/components/app/transaction-list/index.scss
@@ -33,7 +33,7 @@
&__empty {
flex: 1;
display: grid;
- grid-template-rows: 35% 1fr;
+ grid-template-rows: auto;
padding-top: 8px;
}
diff --git a/ui/app/components/transaction-list/transaction-list.component.js b/ui/app/components/app/transaction-list/transaction-list.component.js
index eef60186d..fc5488884 100644
--- a/ui/app/components/transaction-list/transaction-list.component.js
+++ b/ui/app/components/app/transaction-list/transaction-list.component.js
@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import TransactionListItem from '../transaction-list-item'
import ShapeShiftTransactionListItem from '../shift-list-item'
-import { TRANSACTION_TYPE_SHAPESHIFT } from '../../constants/transactions'
+import { TRANSACTION_TYPE_SHAPESHIFT } from '../../../helpers/constants/transactions'
export default class TransactionList extends PureComponent {
static contextTypes = {
@@ -12,13 +12,11 @@ export default class TransactionList extends PureComponent {
static defaultProps = {
pendingTransactions: [],
completedTransactions: [],
- transactionToRetry: {},
}
static propTypes = {
pendingTransactions: PropTypes.array,
completedTransactions: PropTypes.array,
- transactionToRetry: PropTypes.object,
selectedToken: PropTypes.object,
updateNetworkNonce: PropTypes.func,
assetImages: PropTypes.object,
@@ -37,26 +35,34 @@ export default class TransactionList extends PureComponent {
}
}
- shouldShowRetry = transaction => {
- const { transactionToRetry } = this.props
- const { id, submittedTime } = transaction
- return id === transactionToRetry.id && Date.now() - submittedTime > 30000
+ shouldShowRetry = (transactionGroup, isEarliestNonce) => {
+ const { transactions = [], hasRetried } = transactionGroup
+ const [earliestTransaction = {}] = transactions
+ const { submittedTime } = earliestTransaction
+ return Date.now() - submittedTime > 30000 && isEarliestNonce && !hasRetried
+ }
+
+ shouldShowCancel (transactionGroup) {
+ const { hasCancelled } = transactionGroup
+ return !hasCancelled
}
renderTransactions () {
const { t } = this.context
const { pendingTransactions = [], completedTransactions = [] } = this.props
+ const pendingLength = pendingTransactions.length
+
return (
<div className="transaction-list__transactions">
{
- pendingTransactions.length > 0 && (
+ pendingLength > 0 && (
<div className="transaction-list__pending-transactions">
<div className="transaction-list__header">
{ `${t('queue')} (${pendingTransactions.length})` }
</div>
{
- pendingTransactions.map((transaction, index) => (
- this.renderTransaction(transaction, index, true)
+ pendingTransactions.map((transactionGroup, index) => (
+ this.renderTransaction(transactionGroup, index, true)
))
}
</div>
@@ -68,8 +74,8 @@ export default class TransactionList extends PureComponent {
</div>
{
completedTransactions.length > 0
- ? completedTransactions.map((transaction, index) => (
- this.renderTransaction(transaction, index)
+ ? completedTransactions.map((transactionGroup, index) => (
+ this.renderTransaction(transactionGroup, index)
))
: this.renderEmpty()
}
@@ -78,21 +84,22 @@ export default class TransactionList extends PureComponent {
)
}
- renderTransaction (transaction, index, showCancel) {
+ renderTransaction (transactionGroup, index, isPendingTx = false) {
const { selectedToken, assetImages } = this.props
+ const { transactions = [] } = transactionGroup
- return transaction.key === TRANSACTION_TYPE_SHAPESHIFT
+ return transactions[0].key === TRANSACTION_TYPE_SHAPESHIFT
? (
<ShapeShiftTransactionListItem
- { ...transaction }
+ { ...transactions[0] }
key={`shapeshift${index}`}
/>
) : (
<TransactionListItem
- transaction={transaction}
- key={transaction.id}
- showRetry={this.shouldShowRetry(transaction)}
- showCancel={showCancel}
+ transactionGroup={transactionGroup}
+ key={`${transactionGroup.nonce}:${index}`}
+ showRetry={isPendingTx && this.shouldShowRetry(transactionGroup, index === 0)}
+ showCancel={isPendingTx && this.shouldShowCancel(transactionGroup)}
token={selectedToken}
assetImages={assetImages}
/>
diff --git a/ui/app/components/transaction-list/transaction-list.container.js b/ui/app/components/app/transaction-list/transaction-list.container.js
index 2e946c67d..67a24588b 100644
--- a/ui/app/components/transaction-list/transaction-list.container.js
+++ b/ui/app/components/app/transaction-list/transaction-list.container.js
@@ -3,24 +3,17 @@ import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
import TransactionList from './transaction-list.component'
import {
- pendingTransactionsSelector,
- submittedPendingTransactionsSelector,
- completedTransactionsSelector,
-} from '../../selectors/transactions'
-import { getSelectedAddress, getAssetImages } from '../../selectors'
-import { selectedTokenSelector } from '../../selectors/tokens'
-import { getLatestSubmittedTxWithNonce } from '../../helpers/transactions.util'
-import { updateNetworkNonce } from '../../actions'
+ nonceSortedCompletedTransactionsSelector,
+ nonceSortedPendingTransactionsSelector,
+} from '../../../selectors/transactions'
+import { getSelectedAddress, getAssetImages } from '../../../selectors/selectors'
+import { selectedTokenSelector } from '../../../selectors/tokens'
+import { updateNetworkNonce } from '../../../store/actions'
const mapStateToProps = state => {
- const pendingTransactions = pendingTransactionsSelector(state)
- const submittedPendingTransactions = submittedPendingTransactionsSelector(state)
- const networkNonce = state.appState.networkNonce
-
return {
- completedTransactions: completedTransactionsSelector(state),
- pendingTransactions,
- transactionToRetry: getLatestSubmittedTxWithNonce(submittedPendingTransactions, networkNonce),
+ completedTransactions: nonceSortedCompletedTransactionsSelector(state),
+ pendingTransactions: nonceSortedPendingTransactionsSelector(state),
selectedToken: selectedTokenSelector(state),
selectedAddress: getSelectedAddress(state),
assetImages: getAssetImages(state),
diff --git a/ui/app/components/transaction-status/index.js b/ui/app/components/app/transaction-status/index.js
index dece41e9c..dece41e9c 100644
--- a/ui/app/components/transaction-status/index.js
+++ b/ui/app/components/app/transaction-status/index.js
diff --git a/ui/app/components/transaction-status/index.scss b/ui/app/components/app/transaction-status/index.scss
index 26a1f5d38..e7daafeef 100644
--- a/ui/app/components/transaction-status/index.scss
+++ b/ui/app/components/app/transaction-status/index.scss
@@ -1,6 +1,6 @@
.transaction-status {
height: 26px;
- width: 81px;
+ width: 84px;
border-radius: 4px;
background-color: #f0f0f0;
color: #5e6064;
@@ -12,22 +12,34 @@
@media screen and (max-width: $break-small) {
height: 16px;
- width: 70px;
+ width: 72px;
font-size: .5rem;
}
&--confirmed {
background-color: #eafad7;
color: #609a1c;
+
+ .transaction-status__transaction-count {
+ border: 1px solid #609a1c;
+ }
}
&--approved, &--submitted {
background-color: #FFF2DB;
color: #CA810A;
+
+ .transaction-status__transaction-count {
+ border: 1px solid #CA810A;
+ }
}
&--failed {
background: lighten($monzo, 56%);
color: $monzo;
+
+ .transaction-status__transaction-count {
+ border: 1px solid $monzo;
+ }
}
}
diff --git a/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js b/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js
new file mode 100644
index 000000000..ec1d580bd
--- /dev/null
+++ b/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js
@@ -0,0 +1,33 @@
+import React from 'react'
+import assert from 'assert'
+import { mount } from 'enzyme'
+import TransactionStatus from '../transaction-status.component'
+import Tooltip from '../../../ui/tooltip-v2'
+
+describe('TransactionStatus Component', () => {
+ it('should render APPROVED properly', () => {
+ const wrapper = mount(
+ <TransactionStatus
+ statusKey="approved"
+ title="test-title"
+ />,
+ { context: { t: str => str.toUpperCase() } }
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.text(), 'APPROVED')
+ assert.equal(wrapper.find(Tooltip).props().title, 'test-title')
+ })
+
+ it('should render SUBMITTED properly', () => {
+ const wrapper = mount(
+ <TransactionStatus
+ statusKey="submitted"
+ />,
+ { context: { t: str => str.toUpperCase() } }
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.text(), 'PENDING')
+ })
+})
diff --git a/ui/app/components/transaction-status/transaction-status.component.js b/ui/app/components/app/transaction-status/transaction-status.component.js
index c22baf18a..d3a239539 100644
--- a/ui/app/components/transaction-status/transaction-status.component.js
+++ b/ui/app/components/app/transaction-status/transaction-status.component.js
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-import Tooltip from '../tooltip-v2'
+import Tooltip from '../../ui/tooltip-v2'
import {
UNAPPROVED_STATUS,
REJECTED_STATUS,
@@ -11,7 +11,8 @@ import {
CONFIRMED_STATUS,
FAILED_STATUS,
DROPPED_STATUS,
-} from '../../constants/transactions'
+ CANCELLED_STATUS,
+} from '../../../helpers/constants/transactions'
const statusToClassNameHash = {
[UNAPPROVED_STATUS]: 'transaction-status--unapproved',
@@ -22,10 +23,10 @@ const statusToClassNameHash = {
[CONFIRMED_STATUS]: 'transaction-status--confirmed',
[FAILED_STATUS]: 'transaction-status--failed',
[DROPPED_STATUS]: 'transaction-status--dropped',
+ [CANCELLED_STATUS]: 'transaction-status--failed',
}
const statusToTextHash = {
- [APPROVED_STATUS]: 'pending',
[SUBMITTED_STATUS]: 'pending',
}
@@ -50,7 +51,10 @@ export default class TransactionStatus extends PureComponent {
return (
<div className={classnames('transaction-status', className, statusToClassNameHash[statusKey])}>
- <Tooltip position="top" title={title}>
+ <Tooltip
+ position="top"
+ title={title}
+ >
{ statusText }
</Tooltip>
</div>
diff --git a/ui/app/components/transaction-view-balance/index.js b/ui/app/components/app/transaction-view-balance/index.js
index 8824737f7..8824737f7 100644
--- a/ui/app/components/transaction-view-balance/index.js
+++ b/ui/app/components/app/transaction-view-balance/index.js
diff --git a/ui/app/components/transaction-view-balance/index.scss b/ui/app/components/app/transaction-view-balance/index.scss
index 659f896ff..bdcd536b0 100644
--- a/ui/app/components/transaction-view-balance/index.scss
+++ b/ui/app/components/app/transaction-view-balance/index.scss
@@ -6,27 +6,29 @@
height: 54px;
min-width: 0;
+ @media screen and (max-width: $break-small) {
+ flex-direction: column;
+ height: initial;
+ width: 100%;
+ }
+
&__balance {
margin: 0 12px;
display: flex;
flex-direction: column;
min-width: 0;
+ position: relative;
@media screen and (max-width: $break-small) {
align-items: center;
margin: 16px 0;
+ padding: 0 16px;
+ max-width: 100%;
}
}
- &__token-balance {
- margin-left: 12px;
- font-size: 1.5rem;
-
- @media screen and (max-width: $break-small) {
- margin: 12px 0;
- margin-left: 0;
- font-size: 1.75rem;
- }
+ &__primary-container {
+ display: flex;
}
&__primary-balance {
@@ -34,9 +36,24 @@
@media screen and (max-width: $break-small) {
font-size: 1.75rem;
+ width: 100%;
+ justify-content: center;
}
}
+ &__cached-star {
+ margin-left: 4px;
+ }
+
+ &__cached-balance, &__cached-star {
+ color: $web-orange;
+ }
+
+ &__cached-secondary-balance {
+ color: rgba(220, 153, 18, 0.6901960784313725);
+ font-size: 1.15rem;
+ }
+
&__secondary-balance {
font-size: 1.15rem;
color: #a0a0a0;
@@ -51,6 +68,7 @@
@media screen and (max-width: $break-small) {
flex-direction: column;
+ width: 100%;
}
}
@@ -71,9 +89,4 @@
margin-right: 12px;
}
}
-
- @media screen and (max-width: $break-small) {
- flex-direction: column;
- height: initial
- }
}
diff --git a/ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js b/ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js
index 513a8aac9..0e2882e9c 100644
--- a/ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js
+++ b/ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js
@@ -2,9 +2,9 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import sinon from 'sinon'
-import TokenBalance from '../../token-balance'
+import TokenBalance from '../../../ui/token-balance'
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
-import { SEND_ROUTE } from '../../../routes'
+import { SEND_ROUTE } from '../../../../helpers/constants/routes'
import TransactionViewBalance from '../transaction-view-balance.component'
const propsMethodSpies = {
@@ -16,6 +16,7 @@ const historySpies = {
}
const t = (str1, str2) => str2 ? str1 + str2 : str1
+const metricsEvent = () => ({})
describe('TransactionViewBalance Component', () => {
afterEach(() => {
@@ -31,7 +32,7 @@ describe('TransactionViewBalance Component', () => {
ethBalance={123}
fiatBalance={456}
currentCurrency="usd"
- />, { context: { t } })
+ />, { context: { t, metricsEvent } })
assert.equal(wrapper.find('.transaction-view-balance').length, 1)
assert.equal(wrapper.find('.transaction-view-balance__button').length, 2)
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
new file mode 100644
index 000000000..8559e2233
--- /dev/null
+++ b/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js
@@ -0,0 +1,145 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import Button from '../../ui/button'
+import Identicon from '../../ui/identicon'
+import TokenBalance from '../../ui/token-balance'
+import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
+import { SEND_ROUTE } from '../../../helpers/constants/routes'
+import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'
+import Tooltip from '../../ui/tooltip-v2'
+
+export default class TransactionViewBalance extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ metricsEvent: PropTypes.func,
+ }
+
+ static propTypes = {
+ showDepositModal: PropTypes.func,
+ selectedToken: PropTypes.object,
+ history: PropTypes.object,
+ network: PropTypes.string,
+ balance: PropTypes.string,
+ assetImage: PropTypes.string,
+ balanceIsCached: PropTypes.bool,
+ showFiat: PropTypes.bool,
+ }
+
+ static defaultProps = {
+ showFiat: true,
+ }
+
+ renderBalance () {
+ const { selectedToken, balance, balanceIsCached, showFiat } = this.props
+
+ return selectedToken
+ ? (
+ <div className="transaction-view-balance__balance">
+ <TokenBalance
+ token={selectedToken}
+ withSymbol
+ className="transaction-view-balance__primary-balance"
+ />
+ </div>
+ ) : (
+ <Tooltip position="top" title={this.context.t('balanceOutdated')} disabled={!balanceIsCached}>
+ <div className="transaction-view-balance__balance">
+ <div className="transaction-view-balance__primary-container">
+ <UserPreferencedCurrencyDisplay
+ className={classnames('transaction-view-balance__primary-balance', {
+ 'transaction-view-balance__cached-balance': balanceIsCached,
+ })}
+ value={balance}
+ type={PRIMARY}
+ ethNumberOfDecimals={4}
+ hideTitle={true}
+ />
+ {
+ balanceIsCached ? <span className="transaction-view-balance__cached-star">*</span> : null
+ }
+ </div>
+ {
+ showFiat && (
+ <UserPreferencedCurrencyDisplay
+ className={classnames({
+ 'transaction-view-balance__cached-secondary-balance': balanceIsCached,
+ 'transaction-view-balance__secondary-balance': !balanceIsCached,
+ })}
+ value={balance}
+ type={SECONDARY}
+ ethNumberOfDecimals={4}
+ hideTitle={true}
+ />
+ )
+ }
+ </div>
+ </Tooltip>
+ )
+ }
+
+ renderButtons () {
+ const { t, metricsEvent } = this.context
+ const { selectedToken, showDepositModal, history } = this.props
+
+ return (
+ <div className="transaction-view-balance__buttons">
+ {
+ !selectedToken && (
+ <Button
+ type="primary"
+ className="transaction-view-balance__button"
+ onClick={() => {
+ metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Home',
+ name: 'Clicked Deposit',
+ },
+ })
+ showDepositModal()
+ }}
+ >
+ { t('deposit') }
+ </Button>
+ )
+ }
+ <Button
+ type="primary"
+ className="transaction-view-balance__button"
+ onClick={() => {
+ metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Home',
+ name: 'Clicked Send',
+ },
+ })
+ history.push(SEND_ROUTE)
+ }}
+ >
+ { t('send') }
+ </Button>
+ </div>
+ )
+ }
+
+ render () {
+ const { network, selectedToken, assetImage } = this.props
+
+ return (
+ <div className="transaction-view-balance">
+ <div className="transaction-view-balance__balance-container">
+ <Identicon
+ diameter={50}
+ address={selectedToken && selectedToken.address}
+ network={network}
+ image={assetImage}
+ />
+ { this.renderBalance() }
+ </div>
+ { this.renderButtons() }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/transaction-view-balance/transaction-view-balance.container.js b/ui/app/components/app/transaction-view-balance/transaction-view-balance.container.js
index cb8078ec1..41a4525dc 100644
--- a/ui/app/components/transaction-view-balance/transaction-view-balance.container.js
+++ b/ui/app/components/app/transaction-view-balance/transaction-view-balance.container.js
@@ -2,12 +2,24 @@ import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
import TransactionViewBalance from './transaction-view-balance.component'
-import { getSelectedToken, getSelectedAddress, getNativeCurrency, getSelectedTokenAssetImage } from '../../selectors'
-import { showModal } from '../../actions'
+import {
+ getSelectedToken,
+ getSelectedAddress,
+ getNativeCurrency,
+ getSelectedTokenAssetImage,
+ getMetaMaskAccounts,
+ isBalanceCached,
+ preferencesSelector,
+ getIsMainnet,
+} from '../../../selectors/selectors'
+import { showModal } from '../../../store/actions'
const mapStateToProps = state => {
+ const { showFiatInTestnets } = preferencesSelector(state)
+ const isMainnet = getIsMainnet(state)
const selectedAddress = getSelectedAddress(state)
- const { metamask: { network, accounts } } = state
+ const { metamask: { network } } = state
+ const accounts = getMetaMaskAccounts(state)
const account = accounts[selectedAddress]
const { balance } = account
@@ -17,6 +29,8 @@ const mapStateToProps = state => {
balance,
nativeCurrency: getNativeCurrency(state),
assetImage: getSelectedTokenAssetImage(state),
+ balanceIsCached: isBalanceCached(state),
+ showFiat: (isMainnet || !!showFiatInTestnets),
}
}
diff --git a/ui/app/components/transaction-view/index.js b/ui/app/components/app/transaction-view/index.js
index 9eb0c3c83..9eb0c3c83 100644
--- a/ui/app/components/transaction-view/index.js
+++ b/ui/app/components/app/transaction-view/index.js
diff --git a/ui/app/components/transaction-view/index.scss b/ui/app/components/app/transaction-view/index.scss
index 13187f0e5..13187f0e5 100644
--- a/ui/app/components/transaction-view/index.scss
+++ b/ui/app/components/app/transaction-view/index.scss
diff --git a/ui/app/components/transaction-view/transaction-view.component.js b/ui/app/components/app/transaction-view/transaction-view.component.js
index 7014ca173..7014ca173 100644
--- a/ui/app/components/transaction-view/transaction-view.component.js
+++ b/ui/app/components/app/transaction-view/transaction-view.component.js
diff --git a/ui/app/components/app/ui-migration-annoucement/index.js b/ui/app/components/app/ui-migration-annoucement/index.js
new file mode 100644
index 000000000..c6c8cc619
--- /dev/null
+++ b/ui/app/components/app/ui-migration-annoucement/index.js
@@ -0,0 +1 @@
+export {default} from './ui-migration-announcement.container'
diff --git a/ui/app/components/app/ui-migration-annoucement/index.scss b/ui/app/components/app/ui-migration-annoucement/index.scss
new file mode 100644
index 000000000..6138a3079
--- /dev/null
+++ b/ui/app/components/app/ui-migration-annoucement/index.scss
@@ -0,0 +1,22 @@
+.ui-migration-announcement {
+ position: absolute;
+ z-index: 9999;
+ width: 100vw;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ background: $white;
+
+ p {
+ box-sizing: border-box;
+ padding: 1em;
+ font-size: 12pt;
+ }
+
+ p:last-of-type {
+ cursor: pointer;
+ text-decoration: underline;
+ font-weight: bold;
+ }
+}
diff --git a/ui/app/components/app/ui-migration-annoucement/ui-migration-annoucement.component.js b/ui/app/components/app/ui-migration-annoucement/ui-migration-annoucement.component.js
new file mode 100644
index 000000000..7a4124972
--- /dev/null
+++ b/ui/app/components/app/ui-migration-annoucement/ui-migration-annoucement.component.js
@@ -0,0 +1,33 @@
+import PropTypes from 'prop-types'
+import React, {PureComponent} from 'react'
+
+export default class UiMigrationAnnouncement extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func.isRequired,
+ }
+
+ static defaultProps = {
+ shouldShowAnnouncement: true,
+ };
+
+ static propTypes = {
+ onClose: PropTypes.func.isRequired,
+ shouldShowAnnouncement: PropTypes.bool,
+ }
+
+ render () {
+ const { t } = this.context
+ const { onClose, shouldShowAnnouncement } = this.props
+
+ if (!shouldShowAnnouncement) {
+ return null
+ }
+
+ return (
+ <div className="ui-migration-announcement">
+ <p>{t('uiMigrationAnnouncement')}</p>
+ <p onClick={onClose}>{t('close')}</p>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/app/ui-migration-annoucement/ui-migration-announcement.container.js b/ui/app/components/app/ui-migration-annoucement/ui-migration-announcement.container.js
new file mode 100644
index 000000000..55efd5a44
--- /dev/null
+++ b/ui/app/components/app/ui-migration-annoucement/ui-migration-announcement.container.js
@@ -0,0 +1,21 @@
+import { connect } from 'react-redux'
+import UiMigrationAnnouncement from './ui-migration-annoucement.component'
+import { setCompletedUiMigration } from '../../../store/actions'
+
+const mapStateToProps = (state) => {
+ const shouldShowAnnouncement = !state.metamask.completedUiMigration
+
+ return {
+ shouldShowAnnouncement,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onClose () {
+ dispatch(setCompletedUiMigration())
+ },
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(UiMigrationAnnouncement)
diff --git a/ui/app/components/user-preferenced-currency-display/index.js b/ui/app/components/app/user-preferenced-currency-display/index.js
index 0deddaecf..0deddaecf 100644
--- a/ui/app/components/user-preferenced-currency-display/index.js
+++ b/ui/app/components/app/user-preferenced-currency-display/index.js
diff --git a/ui/app/components/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js
index ead584c26..51b2a3c4f 100644
--- a/ui/app/components/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js
+++ b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js
@@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display.component'
-import CurrencyDisplay from '../../currency-display'
+import CurrencyDisplay from '../../../ui/currency-display'
describe('UserPreferencedCurrencyDisplay Component', () => {
describe('rendering', () => {
diff --git a/ui/app/components/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 ba1c23d83..88d63baae 100644
--- a/ui/app/components/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
@@ -21,6 +21,10 @@ describe('UserPreferencedCurrencyDisplay container', () => {
nativeCurrency: 'ETH',
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
+ showFiatInTestnets: false,
+ },
+ provider: {
+ type: 'mainnet',
},
},
}
@@ -28,6 +32,30 @@ describe('UserPreferencedCurrencyDisplay container', () => {
assert.deepEqual(mapStateToProps(mockState), {
nativeCurrency: 'ETH',
useNativeCurrencyAsPrimaryCurrency: true,
+ isMainnet: true,
+ showFiatInTestnets: false,
+ })
+ })
+
+ it('should return the correct props when not in mainnet and showFiatInTestnets is true', () => {
+ const mockState = {
+ metamask: {
+ nativeCurrency: 'ETH',
+ preferences: {
+ useNativeCurrencyAsPrimaryCurrency: true,
+ showFiatInTestnets: true,
+ },
+ provider: {
+ type: 'rinkeby',
+ },
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ nativeCurrency: 'ETH',
+ useNativeCurrencyAsPrimaryCurrency: true,
+ isMainnet: false,
+ showFiatInTestnets: true,
})
})
})
@@ -41,6 +69,8 @@ describe('UserPreferencedCurrencyDisplay container', () => {
stateProps: {
useNativeCurrencyAsPrimaryCurrency: true,
nativeCurrency: 'ETH',
+ isMainnet: true,
+ showFiatInTestnets: false,
},
ownProps: {
type: 'PRIMARY',
@@ -56,6 +86,8 @@ describe('UserPreferencedCurrencyDisplay container', () => {
stateProps: {
useNativeCurrencyAsPrimaryCurrency: false,
nativeCurrency: 'ETH',
+ isMainnet: true,
+ showFiatInTestnets: false,
},
ownProps: {
type: 'PRIMARY',
@@ -71,6 +103,8 @@ describe('UserPreferencedCurrencyDisplay container', () => {
stateProps: {
useNativeCurrencyAsPrimaryCurrency: true,
nativeCurrency: 'ETH',
+ isMainnet: true,
+ showFiatInTestnets: false,
},
ownProps: {
type: 'SECONDARY',
@@ -88,6 +122,8 @@ describe('UserPreferencedCurrencyDisplay container', () => {
stateProps: {
useNativeCurrencyAsPrimaryCurrency: false,
nativeCurrency: 'ETH',
+ isMainnet: true,
+ showFiatInTestnets: false,
},
ownProps: {
type: 'SECONDARY',
@@ -103,6 +139,57 @@ describe('UserPreferencedCurrencyDisplay container', () => {
prefix: 'b',
},
},
+ {
+ stateProps: {
+ useNativeCurrencyAsPrimaryCurrency: false,
+ nativeCurrency: 'ETH',
+ isMainnet: false,
+ showFiatInTestnets: false,
+ },
+ ownProps: {
+ type: 'PRIMARY',
+ },
+ result: {
+ currency: 'ETH',
+ nativeCurrency: 'ETH',
+ numberOfDecimals: 6,
+ prefix: undefined,
+ },
+ },
+ {
+ stateProps: {
+ useNativeCurrencyAsPrimaryCurrency: false,
+ nativeCurrency: 'ETH',
+ isMainnet: false,
+ showFiatInTestnets: true,
+ },
+ ownProps: {
+ type: 'PRIMARY',
+ },
+ result: {
+ currency: undefined,
+ nativeCurrency: 'ETH',
+ numberOfDecimals: 2,
+ prefix: undefined,
+ },
+ },
+ {
+ stateProps: {
+ useNativeCurrencyAsPrimaryCurrency: false,
+ nativeCurrency: 'ETH',
+ isMainnet: true,
+ showFiatInTestnets: true,
+ },
+ ownProps: {
+ type: 'PRIMARY',
+ },
+ result: {
+ currency: undefined,
+ nativeCurrency: 'ETH',
+ numberOfDecimals: 2,
+ prefix: undefined,
+ },
+ },
]
tests.forEach(({ stateProps, ownProps, result }) => {
diff --git a/ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.component.js b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js
index f2a834ea7..4b64b26c0 100644
--- a/ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.component.js
+++ b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import { PRIMARY, SECONDARY, ETH } from '../../constants/common'
-import CurrencyDisplay from '../currency-display'
+import { PRIMARY, SECONDARY, ETH } from '../../../helpers/constants/common'
+import CurrencyDisplay from '../../ui/currency-display'
export default class UserPreferencedCurrencyDisplay extends PureComponent {
static propTypes = {
@@ -10,6 +10,7 @@ export default class UserPreferencedCurrencyDisplay extends PureComponent {
value: PropTypes.string,
numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
hideLabel: PropTypes.bool,
+ hideTitle: PropTypes.bool,
style: PropTypes.object,
showEthLogo: PropTypes.bool,
ethLogoHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
diff --git a/ui/app/components/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 7999301ad..42d156f92 100644
--- a/ui/app/components/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
@@ -1,19 +1,26 @@
import { connect } from 'react-redux'
import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display.component'
-import { preferencesSelector } from '../../selectors'
-import { ETH, PRIMARY, SECONDARY } from '../../constants/common'
+import { preferencesSelector, getIsMainnet } from '../../../selectors/selectors'
+import { ETH, PRIMARY, SECONDARY } from '../../../helpers/constants/common'
const mapStateToProps = (state, ownProps) => {
- const { useNativeCurrencyAsPrimaryCurrency } = preferencesSelector(state)
+ const {
+ useNativeCurrencyAsPrimaryCurrency,
+ showFiatInTestnets,
+ } = preferencesSelector(state)
+
+ const isMainnet = getIsMainnet(state)
return {
useNativeCurrencyAsPrimaryCurrency,
+ showFiatInTestnets,
+ isMainnet,
nativeCurrency: state.metamask.nativeCurrency,
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
- const { useNativeCurrencyAsPrimaryCurrency, nativeCurrency, ...restStateProps } = stateProps
+ const { useNativeCurrencyAsPrimaryCurrency, showFiatInTestnets, isMainnet, nativeCurrency, ...restStateProps } = stateProps
const {
type,
numberOfDecimals: propsNumberOfDecimals,
@@ -40,6 +47,12 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
prefix = propsPrefix || fiatPrefix
}
+ if (!isMainnet && !showFiatInTestnets) {
+ currency = nativeCurrency || ETH
+ numberOfDecimals = propsNumberOfDecimals || ethNumberOfDecimals || 6
+ prefix = propsPrefix || ethPrefix
+ }
+
return {
...restStateProps,
...dispatchProps,
diff --git a/ui/app/components/user-preferenced-currency-input/index.js b/ui/app/components/app/user-preferenced-currency-input/index.js
index 4dc70db3d..4dc70db3d 100644
--- a/ui/app/components/user-preferenced-currency-input/index.js
+++ b/ui/app/components/app/user-preferenced-currency-input/index.js
diff --git a/ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js b/ui/app/components/app/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js
index 710b5d519..3802e16f3 100644
--- a/ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js
+++ b/ui/app/components/app/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js
@@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import UserPreferencedCurrencyInput from '../user-preferenced-currency-input.component'
-import CurrencyInput from '../../currency-input'
+import CurrencyInput from '../../../ui/currency-input'
describe('UserPreferencedCurrencyInput Component', () => {
describe('rendering', () => {
diff --git a/ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.container.test.js b/ui/app/components/app/user-preferenced-currency-input/tests/user-preferenced-currency-input.container.test.js
index 959726443..959726443 100644
--- a/ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.container.test.js
+++ b/ui/app/components/app/user-preferenced-currency-input/tests/user-preferenced-currency-input.container.test.js
diff --git a/ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.component.js b/ui/app/components/app/user-preferenced-currency-input/user-preferenced-currency-input.component.js
index 463e66b80..7c0ec1734 100644
--- a/ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.component.js
+++ b/ui/app/components/app/user-preferenced-currency-input/user-preferenced-currency-input.component.js
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import CurrencyInput from '../currency-input'
+import CurrencyInput from '../../ui/currency-input'
export default class UserPreferencedCurrencyInput extends PureComponent {
static propTypes = {
diff --git a/ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.container.js b/ui/app/components/app/user-preferenced-currency-input/user-preferenced-currency-input.container.js
index 0b88eb5a7..72f17fde4 100644
--- a/ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.container.js
+++ b/ui/app/components/app/user-preferenced-currency-input/user-preferenced-currency-input.container.js
@@ -1,6 +1,6 @@
import { connect } from 'react-redux'
import UserPreferencedCurrencyInput from './user-preferenced-currency-input.component'
-import { preferencesSelector } from '../../selectors'
+import { preferencesSelector } from '../../../selectors/selectors'
const mapStateToProps = state => {
const { useNativeCurrencyAsPrimaryCurrency } = preferencesSelector(state)
diff --git a/ui/app/components/user-preferenced-token-input/index.js b/ui/app/components/app/user-preferenced-token-input/index.js
index 54167e633..54167e633 100644
--- a/ui/app/components/user-preferenced-token-input/index.js
+++ b/ui/app/components/app/user-preferenced-token-input/index.js
diff --git a/ui/app/components/user-preferenced-token-input/tests/user-preferenced-token-input.component.test.js b/ui/app/components/app/user-preferenced-token-input/tests/user-preferenced-token-input.component.test.js
index d85bddeeb..41cfd51f9 100644
--- a/ui/app/components/user-preferenced-token-input/tests/user-preferenced-token-input.component.test.js
+++ b/ui/app/components/app/user-preferenced-token-input/tests/user-preferenced-token-input.component.test.js
@@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import UserPreferencedTokenInput from '../user-preferenced-token-input.component'
-import TokenInput from '../../token-input'
+import TokenInput from '../../../ui/token-input'
describe('UserPreferencedCurrencyInput Component', () => {
describe('rendering', () => {
diff --git a/ui/app/components/user-preferenced-token-input/tests/user-preferenced-token-input.container.test.js b/ui/app/components/app/user-preferenced-token-input/tests/user-preferenced-token-input.container.test.js
index 2f89fba90..2f89fba90 100644
--- a/ui/app/components/user-preferenced-token-input/tests/user-preferenced-token-input.container.test.js
+++ b/ui/app/components/app/user-preferenced-token-input/tests/user-preferenced-token-input.container.test.js
diff --git a/ui/app/components/user-preferenced-token-input/user-preferenced-token-input.component.js b/ui/app/components/app/user-preferenced-token-input/user-preferenced-token-input.component.js
index 8f14231ac..24133188d 100644
--- a/ui/app/components/user-preferenced-token-input/user-preferenced-token-input.component.js
+++ b/ui/app/components/app/user-preferenced-token-input/user-preferenced-token-input.component.js
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import TokenInput from '../token-input'
+import TokenInput from '../../ui/token-input'
export default class UserPreferencedTokenInput extends PureComponent {
static propTypes = {
diff --git a/ui/app/components/user-preferenced-token-input/user-preferenced-token-input.container.js b/ui/app/components/app/user-preferenced-token-input/user-preferenced-token-input.container.js
index 3305d4e29..4a20b20d9 100644
--- a/ui/app/components/user-preferenced-token-input/user-preferenced-token-input.container.js
+++ b/ui/app/components/app/user-preferenced-token-input/user-preferenced-token-input.container.js
@@ -1,6 +1,6 @@
import { connect } from 'react-redux'
import UserPreferencedTokenInput from './user-preferenced-token-input.component'
-import { preferencesSelector } from '../../selectors'
+import { preferencesSelector } from '../../../selectors/selectors'
const mapStateToProps = state => {
const { useNativeCurrencyAsPrimaryCurrency } = preferencesSelector(state)
diff --git a/ui/app/components/wallet-view.js b/ui/app/components/app/wallet-view.js
index e050e0ee6..cec8228b1 100644
--- a/ui/app/components/wallet-view.js
+++ b/ui/app/components/app/wallet-view.js
@@ -6,16 +6,16 @@ const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const inherits = require('util').inherits
const classnames = require('classnames')
-const { checksumAddress } = require('../util')
-import Identicon from './identicon'
+const { checksumAddress } = require('../../helpers/utils/util')
+import Identicon from '../ui/identicon'
// const AccountDropdowns = require('./dropdowns/index.js').AccountDropdowns
-const Tooltip = require('./tooltip-v2.js').default
+const Tooltip = require('../ui/tooltip-v2.js').default
const copyToClipboard = require('copy-to-clipboard')
-const actions = require('../actions')
-const BalanceComponent = require('./balance-component')
+const actions = require('../../store/actions')
+import BalanceComponent from '../ui/balance'
const TokenList = require('./token-list')
-const selectors = require('../selectors')
-const { ADD_TOKEN_ROUTE } = require('../routes')
+const selectors = require('../../selectors/selectors')
+const { ADD_TOKEN_ROUTE } = require('../../helpers/constants/routes')
import AddTokenButton from './add-token-button'
@@ -26,6 +26,7 @@ module.exports = compose(
WalletView.contextTypes = {
t: PropTypes.func,
+ metricsEvent: PropTypes.func,
}
WalletView.defaultProps = {
@@ -38,8 +39,7 @@ function mapStateToProps (state) {
network: state.metamask.network,
sidebarOpen: state.appState.sidebar.isOpen,
identities: state.metamask.identities,
- accounts: state.metamask.accounts,
- tokens: state.metamask.tokens,
+ accounts: selectors.getMetaMaskAccounts(state),
keyrings: state.metamask.keyrings,
selectedAddress: selectors.getSelectedAddress(state),
selectedAccount: selectors.getSelectedAccount(state),
@@ -106,10 +106,18 @@ WalletView.prototype.renderAddToken = function () {
hideSidebar,
history,
} = this.props
+ const { metricsEvent } = this.context
return h(AddTokenButton, {
onClick () {
history.push(ADD_TOKEN_ROUTE)
+ metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Token Menu',
+ name: 'Clicked "Add Token"',
+ },
+ })
if (sidebarOpen) {
hideSidebar()
}
@@ -125,10 +133,11 @@ WalletView.prototype.render = function () {
showAccountDetailModal,
hideSidebar,
identities,
+ network,
} = this.props
// temporary logs + fake extra wallets
- const checksummedAddress = checksumAddress(selectedAddress)
+ const checksummedAddress = checksumAddress(selectedAddress, network)
if (!selectedAddress) {
throw new Error('selectedAddress should not be ' + String(selectedAddress))
@@ -196,6 +205,13 @@ WalletView.prototype.render = function () {
}),
onClick: () => {
copyToClipboard(checksummedAddress)
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Navigation',
+ action: 'Home',
+ name: 'Copied Address',
+ },
+ })
this.setState({ hasCopied: true })
setTimeout(() => this.setState({ hasCopied: false }), 3000)
},
diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js
deleted file mode 100644
index 4e2769ee8..000000000
--- a/ui/app/components/balance-component.js
+++ /dev/null
@@ -1,111 +0,0 @@
-const Component = require('react').Component
-const connect = require('react-redux').connect
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-import TokenBalance from './token-balance'
-import Identicon from './identicon'
-import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display'
-import { PRIMARY, SECONDARY } from '../constants/common'
-const { getNativeCurrency, getAssetImages, conversionRateSelector, getCurrentCurrency} = require('../selectors')
-
-const { formatBalance } = require('../util')
-
-module.exports = connect(mapStateToProps)(BalanceComponent)
-
-function mapStateToProps (state) {
- const accounts = state.metamask.accounts
- const network = state.metamask.network
- const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
- const account = accounts[selectedAddress]
-
- return {
- account,
- network,
- nativeCurrency: getNativeCurrency(state),
- conversionRate: conversionRateSelector(state),
- currentCurrency: getCurrentCurrency(state),
- assetImages: getAssetImages(state),
- }
-}
-
-inherits(BalanceComponent, Component)
-function BalanceComponent () {
- Component.call(this)
-}
-
-BalanceComponent.prototype.render = function () {
- const props = this.props
- const { token, network, assetImages } = props
- const address = token && token.address
- const image = assetImages && address ? assetImages[token.address] : undefined
-
- return h('div.balance-container', {}, [
-
- // TODO: balance icon needs to be passed in
- // h('img.balance-icon', {
- // src: '../images/eth_logo.svg',
- // style: {},
- // }),
- h(Identicon, {
- diameter: 50,
- address,
- network,
- image,
- }),
-
- token ? this.renderTokenBalance() : this.renderBalance(),
- ])
-}
-
-BalanceComponent.prototype.renderTokenBalance = function () {
- const { token } = this.props
-
- return h('div.flex-column.balance-display', [
- h('div.token-amount', [ h(TokenBalance, { token }) ]),
- ])
-}
-
-BalanceComponent.prototype.renderBalance = function () {
- const props = this.props
- const { account, nativeCurrency } = props
- const balanceValue = account && account.balance
- const needsParse = 'needsParse' in props ? props.needsParse : true
- const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse, nativeCurrency) : '...'
- const showFiat = 'showFiat' in props ? props.showFiat : true
-
- if (formattedBalance === 'None' || formattedBalance === '...') {
- return h('div.flex-column.balance-display', {}, [
- h('div.token-amount', {
- style: {},
- }, formattedBalance),
- ])
- }
-
- return h('div.flex-column.balance-display', {}, [
- h(UserPreferencedCurrencyDisplay, {
- className: 'token-amount',
- value: balanceValue,
- type: PRIMARY,
- ethNumberOfDecimals: 4,
- }),
-
- showFiat && h(UserPreferencedCurrencyDisplay, {
- value: balanceValue,
- type: SECONDARY,
- ethNumberOfDecimals: 4,
- }),
- ])
-}
-
-BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, conversionRate) {
- if (formattedBalance === 'None') return formattedBalance
- if (conversionRate === 0) return 'N/A'
-
- const splitBalance = formattedBalance.split(' ')
-
- const convertedNumber = (Number(splitBalance[0]) * conversionRate)
- const wholePart = Math.floor(convertedNumber)
- const decimalPart = convertedNumber - wholePart
-
- return wholePart + Number(decimalPart.toPrecision(2))
-}
diff --git a/ui/app/components/confirm-page-container/index.scss b/ui/app/components/confirm-page-container/index.scss
deleted file mode 100644
index af7a5b555..000000000
--- a/ui/app/components/confirm-page-container/index.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-@import './confirm-page-container-content/index';
-
-@import './confirm-page-container-header/index';
-
-@import './confirm-detail-row/index';
diff --git a/ui/app/components/currency-input/index.scss b/ui/app/components/currency-input/index.scss
deleted file mode 100644
index fcb2db461..000000000
--- a/ui/app/components/currency-input/index.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-.currency-input {
- &__conversion-component {
- font-size: 12px;
- line-height: 12px;
- padding-left: 1px;
- }
-}
diff --git a/ui/app/components/currency-input/tests/currency-input.container.test.js b/ui/app/components/currency-input/tests/currency-input.container.test.js
deleted file mode 100644
index 5d72958e6..000000000
--- a/ui/app/components/currency-input/tests/currency-input.container.test.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import assert from 'assert'
-import proxyquire from 'proxyquire'
-
-let mapStateToProps, mergeProps
-
-proxyquire('../currency-input.container.js', {
- 'react-redux': {
- connect: (ms, md, mp) => {
- mapStateToProps = ms
- mergeProps = mp
- return () => ({})
- },
- },
-})
-
-describe('CurrencyInput container', () => {
- describe('mapStateToProps()', () => {
- it('should return the correct props', () => {
- const mockState = {
- metamask: {
- conversionRate: 280.45,
- currentCurrency: 'usd',
- nativeCurrency: 'ETH',
- },
- }
-
- assert.deepEqual(mapStateToProps(mockState), {
- conversionRate: 280.45,
- currentCurrency: 'usd',
- nativeCurrency: 'ETH',
- })
- })
- })
-
- describe('mergeProps()', () => {
- it('should return the correct props', () => {
- const mockStateProps = {
- conversionRate: 280.45,
- currentCurrency: 'usd',
- nativeCurrency: 'ETH',
- }
- const mockDispatchProps = {}
-
- assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, { useFiat: true }), {
- conversionRate: 280.45,
- currentCurrency: 'usd',
- nativeCurrency: 'ETH',
- useFiat: true,
- suffix: 'USD',
- })
-
- assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, {}), {
- conversionRate: 280.45,
- currentCurrency: 'usd',
- nativeCurrency: 'ETH',
- suffix: 'ETH',
- })
- })
- })
-})
diff --git a/ui/app/components/dropdowns/account-dropdown-mini.js b/ui/app/components/dropdowns/account-dropdown-mini.js
deleted file mode 100644
index 261eb0aa2..000000000
--- a/ui/app/components/dropdowns/account-dropdown-mini.js
+++ /dev/null
@@ -1,75 +0,0 @@
-const Component = require('react').Component
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const AccountListItem = require('../send/account-list-item/account-list-item.component').default
-
-module.exports = AccountDropdownMini
-
-inherits(AccountDropdownMini, Component)
-function AccountDropdownMini () {
- Component.call(this)
-}
-
-AccountDropdownMini.prototype.getListItemIcon = function (currentAccount, selectedAccount) {
- const listItemIcon = h(`i.fa.fa-check.fa-lg`, { style: { color: '#02c9b1' } })
-
- return currentAccount.address === selectedAccount.address
- ? listItemIcon
- : null
-}
-
-AccountDropdownMini.prototype.renderDropdown = function () {
- const {
- accounts,
- selectedAccount,
- closeDropdown,
- onSelect,
- } = this.props
-
- return h('div', {}, [
-
- h('div.account-dropdown-mini__close-area', {
- onClick: closeDropdown,
- }),
-
- h('div.account-dropdown-mini__list', {}, [
-
- ...accounts.map(account => h(AccountListItem, {
- account,
- displayBalance: false,
- displayAddress: false,
- handleClick: () => {
- onSelect(account)
- closeDropdown()
- },
- icon: this.getListItemIcon(account, selectedAccount),
- })),
-
- ]),
-
- ])
-}
-
-AccountDropdownMini.prototype.render = function () {
- const {
- selectedAccount,
- openDropdown,
- dropdownOpen,
- } = this.props
-
- return h('div.account-dropdown-mini', {}, [
-
- h(AccountListItem, {
- account: selectedAccount,
- handleClick: openDropdown,
- displayBalance: false,
- displayAddress: false,
- icon: h(`i.fa.fa-caret-down.fa-lg`, { style: { color: '#dedede' } }),
- }),
-
- dropdownOpen && this.renderDropdown(),
-
- ])
-
-}
-
diff --git a/ui/app/components/dropdowns/components/network-dropdown-icon.js b/ui/app/components/dropdowns/components/network-dropdown-icon.js
deleted file mode 100644
index a45da4c10..000000000
--- a/ui/app/components/dropdowns/components/network-dropdown-icon.js
+++ /dev/null
@@ -1,31 +0,0 @@
-const inherits = require('util').inherits
-const Component = require('react').Component
-const h = require('react-hyperscript')
-
-
-inherits(NetworkDropdownIcon, Component)
-module.exports = NetworkDropdownIcon
-
-function NetworkDropdownIcon () {
- Component.call(this)
-}
-
-NetworkDropdownIcon.prototype.render = function () {
- const {
- backgroundColor,
- isSelected,
- innerBorder = 'none',
- diameter = '12',
- } = this.props
-
- return h(`.menu-icon-circle${isSelected ? '--active' : ''}`, {},
- h('div', {
- style: {
- background: backgroundColor,
- border: innerBorder,
- height: `${diameter}px`,
- width: `${diameter}px`,
- },
- })
- )
-}
diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss
deleted file mode 100644
index e27b0f182..000000000
--- a/ui/app/components/index.scss
+++ /dev/null
@@ -1,63 +0,0 @@
-@import './app-header/index';
-
-@import './add-token-button/index';
-
-@import './button-group/index';
-
-@import './card/index';
-
-@import './confirm-page-container/index';
-
-@import './currency-input/index';
-
-@import './currency-display/index';
-
-@import './error-message/index';
-
-@import './export-text-container/index';
-
-@import './identicon/index';
-
-@import './info-box/index';
-
-@import './menu-bar/index';
-
-@import './modal/index';
-
-@import './modals/index';
-
-@import './network-display/index';
-
-@import './page-container/index';
-
-@import './pages/index';
-
-@import './provider-page-container/index';
-
-@import './selected-account/index';
-
-@import './sender-to-recipient/index';
-
-@import './tabs/index';
-
-@import './transaction-activity-log/index';
-
-@import './transaction-breakdown/index';
-
-@import './transaction-view/index';
-
-@import './transaction-view-balance/index';
-
-@import './transaction-list/index';
-
-@import './transaction-list-item/index';
-
-@import './transaction-list-item-details/index';
-
-@import './transaction-status/index';
-
-@import './app-header/index';
-
-@import './sidebars/index';
-
-@import './unit-input/index';
diff --git a/ui/app/components/menu-bar/menu-bar.component.js b/ui/app/components/menu-bar/menu-bar.component.js
deleted file mode 100644
index 7460e8dd5..000000000
--- a/ui/app/components/menu-bar/menu-bar.component.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import Tooltip from '../tooltip'
-import SelectedAccount from '../selected-account'
-import AccountDetailsDropdown from '../dropdowns/account-details-dropdown.js'
-
-export default class MenuBar extends PureComponent {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- hideSidebar: PropTypes.func,
- isMascara: PropTypes.bool,
- sidebarOpen: PropTypes.bool,
- showSidebar: PropTypes.func,
- }
-
- state = { accountDetailsMenuOpen: false }
-
- render () {
- const { t } = this.context
- const { isMascara, sidebarOpen, hideSidebar, showSidebar } = this.props
- const { accountDetailsMenuOpen } = this.state
-
- return (
- <div className="menu-bar">
- <Tooltip
- title={t('menu')}
- position="bottom"
- >
- <div
- className="fa fa-bars menu-bar__sidebar-button"
- onClick={() => sidebarOpen ? hideSidebar() : showSidebar()}
- />
- </Tooltip>
- <SelectedAccount />
- {
- !isMascara && (
- <Tooltip
- title={t('accountOptions')}
- position="bottom"
- >
- <div
- className="fa fa-ellipsis-h fa-lg menu-bar__open-in-browser"
- onClick={() => this.setState({ accountDetailsMenuOpen: true })}
- >
- </div>
- </Tooltip>
- )
- }
- {
- accountDetailsMenuOpen && (
- <AccountDetailsDropdown
- className="menu-bar__account-details-dropdown"
- onClose={() => this.setState({ accountDetailsMenuOpen: false })}
- />
- )
- }
- </div>
- )
- }
-}
diff --git a/ui/app/components/modals/index.scss b/ui/app/components/modals/index.scss
deleted file mode 100644
index 45453a582..000000000
--- a/ui/app/components/modals/index.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-@import './cancel-transaction/index';
-
-@import './confirm-remove-account/index';
-
-@import './customize-gas/index';
-
-@import './qr-scanner/index';
-
-@import './transaction-confirmed/index';
diff --git a/ui/app/components/modals/welcome-beta/index.js b/ui/app/components/modals/welcome-beta/index.js
deleted file mode 100644
index 49e45b9d7..000000000
--- a/ui/app/components/modals/welcome-beta/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './welcome-beta.container'
diff --git a/ui/app/components/modals/welcome-beta/welcome-beta.container.js b/ui/app/components/modals/welcome-beta/welcome-beta.container.js
deleted file mode 100644
index c5123ad47..000000000
--- a/ui/app/components/modals/welcome-beta/welcome-beta.container.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import WelcomeBeta from './welcome-beta.component'
-import withModalProps from '../../../higher-order-components/with-modal-props'
-
-export default withModalProps(WelcomeBeta)
diff --git a/ui/app/components/pages/add-token/add-token.component.js b/ui/app/components/pages/add-token/add-token.component.js
deleted file mode 100644
index 3612e676c..000000000
--- a/ui/app/components/pages/add-token/add-token.component.js
+++ /dev/null
@@ -1,319 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import ethUtil from 'ethereumjs-util'
-import { checkExistingAddresses } from './util'
-import { tokenInfoGetter } from '../../../token-util'
-import { DEFAULT_ROUTE, CONFIRM_ADD_TOKEN_ROUTE } from '../../../routes'
-import TextField from '../../text-field'
-import TokenList from './token-list'
-import TokenSearch from './token-search'
-import PageContainer from '../../page-container'
-import { Tabs, Tab } from '../../tabs'
-
-const emptyAddr = '0x0000000000000000000000000000000000000000'
-const SEARCH_TAB = 'SEARCH'
-const CUSTOM_TOKEN_TAB = 'CUSTOM_TOKEN'
-
-class AddToken extends Component {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- history: PropTypes.object,
- setPendingTokens: PropTypes.func,
- pendingTokens: PropTypes.object,
- clearPendingTokens: PropTypes.func,
- tokens: PropTypes.array,
- identities: PropTypes.object,
- }
-
- constructor (props) {
- super(props)
-
- this.state = {
- customAddress: '',
- customSymbol: '',
- customDecimals: 0,
- searchResults: [],
- selectedTokens: {},
- tokenSelectorError: null,
- customAddressError: null,
- customSymbolError: null,
- customDecimalsError: null,
- autoFilled: false,
- displayedTab: SEARCH_TAB,
- }
- }
-
- componentDidMount () {
- this.tokenInfoGetter = tokenInfoGetter()
- const { pendingTokens = {} } = this.props
- const pendingTokenKeys = Object.keys(pendingTokens)
-
- if (pendingTokenKeys.length > 0) {
- let selectedTokens = {}
- let customToken = {}
-
- pendingTokenKeys.forEach(tokenAddress => {
- const token = pendingTokens[tokenAddress]
- const { isCustom } = token
-
- if (isCustom) {
- customToken = { ...token }
- } else {
- selectedTokens = { ...selectedTokens, [tokenAddress]: { ...token } }
- }
- })
-
- const {
- address: customAddress = '',
- symbol: customSymbol = '',
- decimals: customDecimals = 0,
- } = customToken
-
- const displayedTab = Object.keys(selectedTokens).length > 0 ? SEARCH_TAB : CUSTOM_TOKEN_TAB
- this.setState({ selectedTokens, customAddress, customSymbol, customDecimals, displayedTab })
- }
- }
-
- handleToggleToken (token) {
- const { address } = token
- const { selectedTokens = {} } = this.state
- const selectedTokensCopy = { ...selectedTokens }
-
- if (address in selectedTokensCopy) {
- delete selectedTokensCopy[address]
- } else {
- selectedTokensCopy[address] = token
- }
-
- this.setState({
- selectedTokens: selectedTokensCopy,
- tokenSelectorError: null,
- })
- }
-
- hasError () {
- const {
- tokenSelectorError,
- customAddressError,
- customSymbolError,
- customDecimalsError,
- } = this.state
-
- return tokenSelectorError || customAddressError || customSymbolError || customDecimalsError
- }
-
- hasSelected () {
- const { customAddress = '', selectedTokens = {} } = this.state
- return customAddress || Object.keys(selectedTokens).length > 0
- }
-
- handleNext () {
- if (this.hasError()) {
- return
- }
-
- if (!this.hasSelected()) {
- this.setState({ tokenSelectorError: this.context.t('mustSelectOne') })
- return
- }
-
- const { setPendingTokens, history } = this.props
- const {
- customAddress: address,
- customSymbol: symbol,
- customDecimals: decimals,
- selectedTokens,
- } = this.state
-
- const customToken = {
- address,
- symbol,
- decimals,
- }
-
- setPendingTokens({ customToken, selectedTokens })
- history.push(CONFIRM_ADD_TOKEN_ROUTE)
- }
-
- async attemptToAutoFillTokenParams (address) {
- const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(address)
-
- const autoFilled = Boolean(symbol && decimals)
- this.setState({ autoFilled })
- this.handleCustomSymbolChange(symbol || '')
- this.handleCustomDecimalsChange(decimals)
- }
-
- handleCustomAddressChange (value) {
- const customAddress = value.trim()
- this.setState({
- customAddress,
- customAddressError: null,
- tokenSelectorError: null,
- autoFilled: false,
- })
-
- const isValidAddress = ethUtil.isValidAddress(customAddress)
- const standardAddress = ethUtil.addHexPrefix(customAddress).toLowerCase()
-
- switch (true) {
- case !isValidAddress:
- this.setState({
- customAddressError: this.context.t('invalidAddress'),
- customSymbol: '',
- customDecimals: 0,
- customSymbolError: null,
- customDecimalsError: null,
- })
-
- break
- case Boolean(this.props.identities[standardAddress]):
- this.setState({
- customAddressError: this.context.t('personalAddressDetected'),
- })
-
- break
- case checkExistingAddresses(customAddress, this.props.tokens):
- this.setState({
- customAddressError: this.context.t('tokenAlreadyAdded'),
- })
-
- break
- default:
- if (customAddress !== emptyAddr) {
- this.attemptToAutoFillTokenParams(customAddress)
- }
- }
- }
-
- handleCustomSymbolChange (value) {
- const customSymbol = value.trim()
- const symbolLength = customSymbol.length
- let customSymbolError = null
-
- if (symbolLength <= 0 || symbolLength >= 10) {
- customSymbolError = this.context.t('symbolBetweenZeroTen')
- }
-
- this.setState({ customSymbol, customSymbolError })
- }
-
- handleCustomDecimalsChange (value) {
- const customDecimals = value.trim()
- const validDecimals = customDecimals !== null &&
- customDecimals !== '' &&
- customDecimals >= 0 &&
- customDecimals <= 36
- let customDecimalsError = null
-
- if (!validDecimals) {
- customDecimalsError = this.context.t('decimalsMustZerotoTen')
- }
-
- this.setState({ customDecimals, customDecimalsError })
- }
-
- renderCustomTokenForm () {
- const {
- customAddress,
- customSymbol,
- customDecimals,
- customAddressError,
- customSymbolError,
- customDecimalsError,
- autoFilled,
- } = this.state
-
- return (
- <div className="add-token__custom-token-form">
- <TextField
- id="custom-address"
- label={this.context.t('tokenAddress')}
- type="text"
- value={customAddress}
- onChange={e => this.handleCustomAddressChange(e.target.value)}
- error={customAddressError}
- fullWidth
- margin="normal"
- />
- <TextField
- id="custom-symbol"
- label={this.context.t('tokenSymbol')}
- type="text"
- value={customSymbol}
- onChange={e => this.handleCustomSymbolChange(e.target.value)}
- error={customSymbolError}
- fullWidth
- margin="normal"
- disabled={autoFilled}
- />
- <TextField
- id="custom-decimals"
- label={this.context.t('decimal')}
- type="number"
- value={customDecimals}
- onChange={e => this.handleCustomDecimalsChange(e.target.value)}
- error={customDecimalsError}
- fullWidth
- margin="normal"
- disabled={autoFilled}
- />
- </div>
- )
- }
-
- renderSearchToken () {
- const { tokenSelectorError, selectedTokens, searchResults } = this.state
-
- return (
- <div className="add-token__search-token">
- <TokenSearch
- onSearch={({ results = [] }) => this.setState({ searchResults: results })}
- error={tokenSelectorError}
- />
- <div className="add-token__token-list">
- <TokenList
- results={searchResults}
- selectedTokens={selectedTokens}
- onToggleToken={token => this.handleToggleToken(token)}
- />
- </div>
- </div>
- )
- }
-
- renderTabs () {
- return (
- <Tabs>
- <Tab name={this.context.t('search')}>
- { this.renderSearchToken() }
- </Tab>
- <Tab name={this.context.t('customToken')}>
- { this.renderCustomTokenForm() }
- </Tab>
- </Tabs>
- )
- }
-
- render () {
- const { history, clearPendingTokens } = this.props
-
- return (
- <PageContainer
- title={this.context.t('addTokens')}
- tabsComponent={this.renderTabs()}
- onSubmit={() => this.handleNext()}
- disabled={this.hasError() || !this.hasSelected()}
- onCancel={() => {
- clearPendingTokens()
- history.push(DEFAULT_ROUTE)
- }}
- />
- )
- }
-}
-
-export default AddToken
diff --git a/ui/app/components/pages/add-token/add-token.container.js b/ui/app/components/pages/add-token/add-token.container.js
deleted file mode 100644
index 87671b156..000000000
--- a/ui/app/components/pages/add-token/add-token.container.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { connect } from 'react-redux'
-import AddToken from './add-token.component'
-
-const { setPendingTokens, clearPendingTokens } = require('../../../actions')
-
-const mapStateToProps = ({ metamask }) => {
- const { identities, tokens, pendingTokens } = metamask
- return {
- identities,
- tokens,
- pendingTokens,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- setPendingTokens: tokens => dispatch(setPendingTokens(tokens)),
- clearPendingTokens: () => dispatch(clearPendingTokens()),
- }
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(AddToken)
diff --git a/ui/app/components/pages/add-token/index.js b/ui/app/components/pages/add-token/index.js
deleted file mode 100644
index 3666cae82..000000000
--- a/ui/app/components/pages/add-token/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import AddToken from './add-token.container'
-module.exports = AddToken
diff --git a/ui/app/components/pages/add-token/index.scss b/ui/app/components/pages/add-token/index.scss
deleted file mode 100644
index 39e86b97b..000000000
--- a/ui/app/components/pages/add-token/index.scss
+++ /dev/null
@@ -1,25 +0,0 @@
-@import './token-list/index';
-
-.add-token {
- &__custom-token-form {
- padding: 8px 16px 16px;
-
- input[type="number"]::-webkit-inner-spin-button {
- -webkit-appearance: none;
- display: none;
- }
-
- input[type="number"]:hover::-webkit-inner-spin-button {
- -webkit-appearance: none;
- display: none;
- }
- }
-
- &__search-token {
- padding: 16px;
- }
-
- &__token-list {
- margin-top: 16px;
- }
-}
diff --git a/ui/app/components/pages/add-token/token-list/index.js b/ui/app/components/pages/add-token/token-list/index.js
deleted file mode 100644
index 21dd5ac72..000000000
--- a/ui/app/components/pages/add-token/token-list/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import TokenList from './token-list.container'
-module.exports = TokenList
diff --git a/ui/app/components/pages/add-token/token-list/index.scss b/ui/app/components/pages/add-token/token-list/index.scss
deleted file mode 100644
index e32739d59..000000000
--- a/ui/app/components/pages/add-token/token-list/index.scss
+++ /dev/null
@@ -1,65 +0,0 @@
-@import './token-list-placeholder/index';
-
-.token-list {
- &__title {
- font-size: .75rem;
- }
-
- &__tokens-container {
- display: flex;
- flex-direction: column;
- }
-
- &__token {
- transition: 200ms ease-in-out;
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
- padding: 8px;
- margin-top: 8px;
- box-sizing: border-box;
- border-radius: 10px;
- cursor: pointer;
- border: 2px solid transparent;
- position: relative;
-
- &:hover {
- border: 2px solid rgba($malibu-blue, .5);
- }
-
- &--selected {
- border: 2px solid $malibu-blue !important;
- }
-
- &--disabled {
- opacity: .4;
- pointer-events: none;
- }
- }
-
- &__token-icon {
- width: 48px;
- height: 48px;
- background-repeat: no-repeat;
- background-size: contain;
- background-position: center;
- border-radius: 50%;
- background-color: $white;
- box-shadow: 0 2px 4px 0 rgba($black, .24);
- margin-right: 12px;
- flex: 0 0 auto;
- }
-
- &__token-data {
- display: flex;
- flex-direction: row;
- align-items: center;
- min-width: 0;
- }
-
- &__token-name {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-}
diff --git a/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.js b/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.js
deleted file mode 100644
index b82f45e93..000000000
--- a/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import TokenListPlaceholder from './token-list-placeholder.component'
-module.exports = TokenListPlaceholder
diff --git a/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.scss b/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.scss
deleted file mode 100644
index cc495dfb0..000000000
--- a/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.scss
+++ /dev/null
@@ -1,23 +0,0 @@
-.token-list-placeholder {
- display: flex;
- align-items: center;
- padding-top: 36px;
- flex-direction: column;
- line-height: 22px;
- opacity: .5;
-
- &__text {
- color: $silver-chalice;
- width: 50%;
- text-align: center;
- margin-top: 8px;
-
- @media screen and (max-width: 575px) {
- width: 60%;
- }
- }
-
- &__link {
- color: $curious-blue;
- }
-}
diff --git a/ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js b/ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js
deleted file mode 100644
index 20f550927..000000000
--- a/ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-
-export default class TokenListPlaceholder extends Component {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- render () {
- return (
- <div className="token-list-placeholder">
- <img src="images/tokensearch.svg" />
- <div className="token-list-placeholder__text">
- { this.context.t('addAcquiredTokens') }
- </div>
- <a
- className="token-list-placeholder__link"
- href="https://metamask.zendesk.com/hc/en-us/articles/360015489031"
- target="_blank"
- rel="noopener noreferrer"
- >
- { this.context.t('learnMore') }
- </a>
- </div>
- )
- }
-}
diff --git a/ui/app/components/pages/add-token/token-list/token-list.component.js b/ui/app/components/pages/add-token/token-list/token-list.component.js
deleted file mode 100644
index 724a68d6e..000000000
--- a/ui/app/components/pages/add-token/token-list/token-list.component.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import classnames from 'classnames'
-import { checkExistingAddresses } from '../util'
-import TokenListPlaceholder from './token-list-placeholder'
-
-export default class InfoBox extends Component {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- tokens: PropTypes.array,
- results: PropTypes.array,
- selectedTokens: PropTypes.object,
- onToggleToken: PropTypes.func,
- }
-
- render () {
- const { results = [], selectedTokens = {}, onToggleToken, tokens = [] } = this.props
-
- return results.length === 0
- ? <TokenListPlaceholder />
- : (
- <div className="token-list">
- <div className="token-list__title">
- { this.context.t('searchResults') }
- </div>
- <div className="token-list__tokens-container">
- {
- Array(6).fill(undefined)
- .map((_, i) => {
- const { logo, symbol, name, address } = results[i] || {}
- const tokenAlreadyAdded = checkExistingAddresses(address, tokens)
-
- return Boolean(logo || symbol || name) && (
- <div
- className={classnames('token-list__token', {
- 'token-list__token--selected': selectedTokens[address],
- 'token-list__token--disabled': tokenAlreadyAdded,
- })}
- onClick={() => !tokenAlreadyAdded && onToggleToken(results[i])}
- key={i}
- >
- <div
- className="token-list__token-icon"
- style={{ backgroundImage: logo && `url(images/contract/${logo})` }}>
- </div>
- <div className="token-list__token-data">
- <span className="token-list__token-name">{ `${name} (${symbol})` }</span>
- </div>
- </div>
- )
- })
- }
- </div>
- </div>
- )
- }
-}
diff --git a/ui/app/components/pages/add-token/token-list/token-list.container.js b/ui/app/components/pages/add-token/token-list/token-list.container.js
deleted file mode 100644
index cd7b07a37..000000000
--- a/ui/app/components/pages/add-token/token-list/token-list.container.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { connect } from 'react-redux'
-import TokenList from './token-list.component'
-
-const mapStateToProps = ({ metamask }) => {
- const { tokens } = metamask
- return {
- tokens,
- }
-}
-
-export default connect(mapStateToProps)(TokenList)
diff --git a/ui/app/components/pages/add-token/token-search/index.js b/ui/app/components/pages/add-token/token-search/index.js
deleted file mode 100644
index acaa6b084..000000000
--- a/ui/app/components/pages/add-token/token-search/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import TokenSearch from './token-search.component'
-module.exports = TokenSearch
diff --git a/ui/app/components/pages/add-token/token-search/token-search.component.js b/ui/app/components/pages/add-token/token-search/token-search.component.js
deleted file mode 100644
index 036b2db1e..000000000
--- a/ui/app/components/pages/add-token/token-search/token-search.component.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import contractMap from 'eth-contract-metadata'
-import Fuse from 'fuse.js'
-import InputAdornment from '@material-ui/core/InputAdornment'
-import TextField from '../../../text-field'
-
-const contractList = Object.entries(contractMap)
- .map(([ _, tokenData]) => tokenData)
- .filter(tokenData => Boolean(tokenData.erc20))
-
-const fuse = new Fuse(contractList, {
- shouldSort: true,
- threshold: 0.45,
- location: 0,
- distance: 100,
- maxPatternLength: 32,
- minMatchCharLength: 1,
- keys: [
- { name: 'name', weight: 0.5 },
- { name: 'symbol', weight: 0.5 },
- ],
-})
-
-export default class TokenSearch extends Component {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static defaultProps = {
- error: null,
- }
-
- static propTypes = {
- onSearch: PropTypes.func,
- error: PropTypes.string,
- }
-
- constructor (props) {
- super(props)
-
- this.state = {
- searchQuery: '',
- }
- }
-
- handleSearch (searchQuery) {
- this.setState({ searchQuery })
- const fuseSearchResult = fuse.search(searchQuery)
- const addressSearchResult = contractList.filter(token => {
- return token.address.toLowerCase() === searchQuery.toLowerCase()
- })
- const results = [...addressSearchResult, ...fuseSearchResult]
- this.props.onSearch({ searchQuery, results })
- }
-
- renderAdornment () {
- return (
- <InputAdornment
- position="start"
- style={{ marginRight: '12px' }}
- >
- <img src="images/search.svg" />
- </InputAdornment>
- )
- }
-
- render () {
- const { error } = this.props
- const { searchQuery } = this.state
-
- return (
- <TextField
- id="search-tokens"
- placeholder={this.context.t('searchTokens')}
- type="text"
- value={searchQuery}
- onChange={e => this.handleSearch(e.target.value)}
- error={error}
- fullWidth
- startAdornment={this.renderAdornment()}
- />
- )
- }
-}
diff --git a/ui/app/components/pages/add-token/util.js b/ui/app/components/pages/add-token/util.js
deleted file mode 100644
index 579c56cc0..000000000
--- a/ui/app/components/pages/add-token/util.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import R from 'ramda'
-
-export function checkExistingAddresses (address, tokenList = []) {
- if (!address) {
- return false
- }
-
- const matchesAddress = existingToken => {
- return existingToken.address.toLowerCase() === address.toLowerCase()
- }
-
- return R.any(matchesAddress)(tokenList)
-}
diff --git a/ui/app/components/pages/authenticated.js b/ui/app/components/pages/authenticated.js
deleted file mode 100644
index 1f6b0be49..000000000
--- a/ui/app/components/pages/authenticated.js
+++ /dev/null
@@ -1,34 +0,0 @@
-const { connect } = require('react-redux')
-const PropTypes = require('prop-types')
-const { Redirect } = require('react-router-dom')
-const h = require('react-hyperscript')
-const MetamaskRoute = require('./metamask-route')
-const { UNLOCK_ROUTE, INITIALIZE_ROUTE } = require('../../routes')
-
-const Authenticated = props => {
- const { isUnlocked, isInitialized } = props
-
- switch (true) {
- case isUnlocked && isInitialized:
- return h(MetamaskRoute, { ...props })
- case !isInitialized:
- return h(Redirect, { to: { pathname: INITIALIZE_ROUTE } })
- default:
- return h(Redirect, { to: { pathname: UNLOCK_ROUTE } })
- }
-}
-
-Authenticated.propTypes = {
- isUnlocked: PropTypes.bool,
- isInitialized: PropTypes.bool,
-}
-
-const mapStateToProps = state => {
- const { metamask: { isUnlocked, isInitialized } } = state
- return {
- isUnlocked,
- isInitialized,
- }
-}
-
-module.exports = connect(mapStateToProps)(Authenticated)
diff --git a/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js b/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js
deleted file mode 100644
index ee5d6fa64..000000000
--- a/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import { DEFAULT_ROUTE } from '../../../routes'
-import Button from '../../button'
-import Identicon from '../../../components/identicon'
-import TokenBalance from '../../token-balance'
-
-export default class ConfirmAddSuggestedToken extends Component {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- history: PropTypes.object,
- clearPendingTokens: PropTypes.func,
- addToken: PropTypes.func,
- pendingTokens: PropTypes.object,
- removeSuggestedTokens: PropTypes.func,
- }
-
- componentDidMount () {
- const { pendingTokens = {}, history } = this.props
-
- if (Object.keys(pendingTokens).length === 0) {
- history.push(DEFAULT_ROUTE)
- }
- }
-
- getTokenName (name, symbol) {
- return typeof name === 'undefined'
- ? symbol
- : `${name} (${symbol})`
- }
-
- render () {
- const { addToken, pendingTokens, removeSuggestedTokens, history } = this.props
- const pendingTokenKey = Object.keys(pendingTokens)[0]
- const pendingToken = pendingTokens[pendingTokenKey]
-
- return (
- <div className="page-container">
- <div className="page-container__header">
- <div className="page-container__title">
- { this.context.t('addSuggestedTokens') }
- </div>
- <div className="page-container__subtitle">
- { this.context.t('likeToAddTokens') }
- </div>
- </div>
- <div className="page-container__content">
- <div className="confirm-add-token">
- <div className="confirm-add-token__header">
- <div className="confirm-add-token__token">
- { this.context.t('token') }
- </div>
- <div className="confirm-add-token__balance">
- { this.context.t('balance') }
- </div>
- </div>
- <div className="confirm-add-token__token-list">
- {
- Object.entries(pendingTokens)
- .map(([ address, token ]) => {
- const { name, symbol, image } = token
-
- return (
- <div
- className="confirm-add-token__token-list-item"
- key={address}
- >
- <div className="confirm-add-token__token confirm-add-token__data">
- <Identicon
- className="confirm-add-token__token-icon"
- diameter={48}
- address={address}
- image={image}
- />
- <div className="confirm-add-token__name">
- { this.getTokenName(name, symbol) }
- </div>
- </div>
- <div className="confirm-add-token__balance">
- <TokenBalance token={token} />
- </div>
- </div>
- )
- })
- }
- </div>
- </div>
- </div>
- <div className="page-container__footer">
- <header>
- <Button
- type="default"
- large
- className="page-container__footer-button"
- onClick={() => {
- removeSuggestedTokens()
- .then(() => history.push(DEFAULT_ROUTE))
- }}
- >
- { this.context.t('cancel') }
- </Button>
- <Button
- type="primary"
- large
- className="page-container__footer-button"
- onClick={() => {
- addToken(pendingToken)
- .then(() => removeSuggestedTokens())
- .then(() => history.push(DEFAULT_ROUTE))
- }}
- >
- { this.context.t('addToken') }
- </Button>
- </header>
- </div>
- </div>
- )
- }
-}
diff --git a/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js b/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js
deleted file mode 100644
index 1f2737e52..000000000
--- a/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { connect } from 'react-redux'
-import { compose } from 'recompose'
-import ConfirmAddSuggestedToken from './confirm-add-suggested-token.component'
-import { withRouter } from 'react-router-dom'
-
-const extend = require('xtend')
-
-const { addToken, removeSuggestedTokens } = require('../../../actions')
-
-const mapStateToProps = ({ metamask }) => {
- const { pendingTokens, suggestedTokens } = metamask
- const params = extend(pendingTokens, suggestedTokens)
-
- return {
- pendingTokens: params,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- addToken: ({address, symbol, decimals, image}) => dispatch(addToken(address, symbol, decimals, image)),
- removeSuggestedTokens: () => dispatch(removeSuggestedTokens()),
- }
-}
-
-export default compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(ConfirmAddSuggestedToken)
diff --git a/ui/app/components/pages/confirm-add-suggested-token/index.js b/ui/app/components/pages/confirm-add-suggested-token/index.js
deleted file mode 100644
index 2ca56b43c..000000000
--- a/ui/app/components/pages/confirm-add-suggested-token/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import ConfirmAddSuggestedToken from './confirm-add-suggested-token.container'
-module.exports = ConfirmAddSuggestedToken
diff --git a/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js b/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js
deleted file mode 100644
index d3fec79d7..000000000
--- a/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js
+++ /dev/null
@@ -1,117 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import { DEFAULT_ROUTE, ADD_TOKEN_ROUTE } from '../../../routes'
-import Button from '../../button'
-import Identicon from '../../identicon'
-import TokenBalance from '../../token-balance'
-
-export default class ConfirmAddToken extends Component {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- history: PropTypes.object,
- clearPendingTokens: PropTypes.func,
- addTokens: PropTypes.func,
- pendingTokens: PropTypes.object,
- }
-
- componentDidMount () {
- const { pendingTokens = {}, history } = this.props
-
- if (Object.keys(pendingTokens).length === 0) {
- history.push(DEFAULT_ROUTE)
- }
- }
-
- getTokenName (name, symbol) {
- return typeof name === 'undefined'
- ? symbol
- : `${name} (${symbol})`
- }
-
- render () {
- const { history, addTokens, clearPendingTokens, pendingTokens } = this.props
-
- return (
- <div className="page-container">
- <div className="page-container__header">
- <div className="page-container__title">
- { this.context.t('addTokens') }
- </div>
- <div className="page-container__subtitle">
- { this.context.t('likeToAddTokens') }
- </div>
- </div>
- <div className="page-container__content">
- <div className="confirm-add-token">
- <div className="confirm-add-token__header">
- <div className="confirm-add-token__token">
- { this.context.t('token') }
- </div>
- <div className="confirm-add-token__balance">
- { this.context.t('balance') }
- </div>
- </div>
- <div className="confirm-add-token__token-list">
- {
- Object.entries(pendingTokens)
- .map(([ address, token ]) => {
- const { name, symbol } = token
-
- return (
- <div
- className="confirm-add-token__token-list-item"
- key={address}
- >
- <div className="confirm-add-token__token confirm-add-token__data">
- <Identicon
- className="confirm-add-token__token-icon"
- diameter={48}
- address={address}
- />
- <div className="confirm-add-token__name">
- { this.getTokenName(name, symbol) }
- </div>
- </div>
- <div className="confirm-add-token__balance">
- <TokenBalance token={token} />
- </div>
- </div>
- )
- })
- }
- </div>
- </div>
- </div>
- <div className="page-container__footer">
- <header>
- <Button
- type="default"
- large
- className="page-container__footer-button"
- onClick={() => history.push(ADD_TOKEN_ROUTE)}
- >
- { this.context.t('back') }
- </Button>
- <Button
- type="primary"
- large
- className="page-container__footer-button"
- onClick={() => {
- addTokens(pendingTokens)
- .then(() => {
- clearPendingTokens()
- history.push(DEFAULT_ROUTE)
- })
- }}
- >
- { this.context.t('addTokens') }
- </Button>
- </header>
- </div>
- </div>
- )
- }
-}
diff --git a/ui/app/components/pages/confirm-add-token/confirm-add-token.container.js b/ui/app/components/pages/confirm-add-token/confirm-add-token.container.js
deleted file mode 100644
index 0190024d9..000000000
--- a/ui/app/components/pages/confirm-add-token/confirm-add-token.container.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import { connect } from 'react-redux'
-import ConfirmAddToken from './confirm-add-token.component'
-
-const { addTokens, clearPendingTokens } = require('../../../actions')
-
-const mapStateToProps = ({ metamask }) => {
- const { pendingTokens } = metamask
- return {
- pendingTokens,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- addTokens: tokens => dispatch(addTokens(tokens)),
- clearPendingTokens: () => dispatch(clearPendingTokens()),
- }
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(ConfirmAddToken)
diff --git a/ui/app/components/pages/confirm-add-token/index.js b/ui/app/components/pages/confirm-add-token/index.js
deleted file mode 100644
index b7decabec..000000000
--- a/ui/app/components/pages/confirm-add-token/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import ConfirmAddToken from './confirm-add-token.container'
-module.exports = ConfirmAddToken
diff --git a/ui/app/components/pages/confirm-add-token/index.scss b/ui/app/components/pages/confirm-add-token/index.scss
deleted file mode 100644
index 66146cf78..000000000
--- a/ui/app/components/pages/confirm-add-token/index.scss
+++ /dev/null
@@ -1,69 +0,0 @@
-.confirm-add-token {
- padding: 16px;
-
- &__header {
- font-size: .75rem;
- display: flex;
- }
-
- &__token {
- flex: 1;
- min-width: 0;
- }
-
- &__balance {
- flex: 0 0 30%;
- min-width: 0;
- }
-
- &__token-list {
- display: flex;
- flex-flow: column nowrap;
-
- .token-balance {
- display: flex;
- flex-flow: row nowrap;
- align-items: flex-start;
-
- &__amount {
- color: $scorpion;
- font-size: 43px;
- line-height: 43px;
- margin-right: 8px;
- }
-
- &__symbol {
- color: $scorpion;
- font-size: 16px;
- font-weight: 400;
- line-height: 24px;
- }
- }
- }
-
- &__token-list-item {
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
- margin-top: 8px;
- box-sizing: border-box;
- }
-
- &__data {
- display: flex;
- align-items: center;
- padding: 8px;
- }
-
- &__name {
- min-width: 0;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- &__token-icon {
- margin-right: 12px;
- flex: 0 0 auto;
- }
-}
diff --git a/ui/app/components/pages/confirm-approve/confirm-approve.component.js b/ui/app/components/pages/confirm-approve/confirm-approve.component.js
deleted file mode 100644
index b71eaa1d4..000000000
--- a/ui/app/components/pages/confirm-approve/confirm-approve.component.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import ConfirmTokenTransactionBase from '../confirm-token-transaction-base'
-
-export default class ConfirmApprove extends Component {
- static propTypes = {
- tokenAmount: PropTypes.number,
- tokenSymbol: PropTypes.string,
- }
-
- render () {
- const { tokenAmount, tokenSymbol } = this.props
-
- return (
- <ConfirmTokenTransactionBase
- tokenAmount={tokenAmount}
- warning={`By approving this action, you grant permission for this contract to spend up to ${tokenAmount} of your ${tokenSymbol}.`}
- />
- )
- }
-}
diff --git a/ui/app/components/pages/confirm-approve/confirm-approve.container.js b/ui/app/components/pages/confirm-approve/confirm-approve.container.js
deleted file mode 100644
index 4ef9f4ced..000000000
--- a/ui/app/components/pages/confirm-approve/confirm-approve.container.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { connect } from 'react-redux'
-import ConfirmApprove from './confirm-approve.component'
-import { approveTokenAmountAndToAddressSelector } from '../../../selectors/confirm-transaction'
-
-const mapStateToProps = state => {
- const { confirmTransaction: { tokenProps: { tokenSymbol } = {} } } = state
- const { tokenAmount } = approveTokenAmountAndToAddressSelector(state)
-
- return {
- tokenAmount,
- tokenSymbol,
- }
-}
-
-export default connect(mapStateToProps)(ConfirmApprove)
diff --git a/ui/app/components/pages/confirm-approve/index.js b/ui/app/components/pages/confirm-approve/index.js
deleted file mode 100644
index 791297be7..000000000
--- a/ui/app/components/pages/confirm-approve/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './confirm-approve.container'
diff --git a/ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.component.js b/ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.component.js
deleted file mode 100644
index 9bc0daab9..000000000
--- a/ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.component.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import ethUtil from 'ethereumjs-util'
-import ConfirmTransactionBase from '../confirm-transaction-base'
-
-export default class ConfirmDeployContract extends Component {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- txData: PropTypes.object,
- }
-
- renderData () {
- const { t } = this.context
- const {
- txData: {
- origin,
- txParams: {
- data,
- } = {},
- } = {},
- } = this.props
-
- return (
- <div className="confirm-page-container-content__data">
- <div className="confirm-page-container-content__data-box">
- <div className="confirm-page-container-content__data-field">
- <div className="confirm-page-container-content__data-field-label">
- { `${t('origin')}:` }
- </div>
- <div>
- { origin }
- </div>
- </div>
- <div className="confirm-page-container-content__data-field">
- <div className="confirm-page-container-content__data-field-label">
- { `${t('bytes')}:` }
- </div>
- <div>
- { ethUtil.toBuffer(data).length }
- </div>
- </div>
- </div>
- <div className="confirm-page-container-content__data-box-label">
- { `${t('hexData')}:` }
- </div>
- <div className="confirm-page-container-content__data-box">
- { data }
- </div>
- </div>
- )
- }
-
- render () {
- return (
- <ConfirmTransactionBase
- action={this.context.t('contractDeployment')}
- dataComponent={this.renderData()}
- />
- )
- }
-}
diff --git a/ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.container.js b/ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.container.js
deleted file mode 100644
index 336ee83ea..000000000
--- a/ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.container.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { connect } from 'react-redux'
-import ConfirmDeployContract from './confirm-deploy-contract.component'
-
-const mapStateToProps = state => {
- const { confirmTransaction: { txData } = {} } = state
-
- return {
- txData,
- }
-}
-
-export default connect(mapStateToProps)(ConfirmDeployContract)
diff --git a/ui/app/components/pages/confirm-deploy-contract/index.js b/ui/app/components/pages/confirm-deploy-contract/index.js
deleted file mode 100644
index c4fb01b52..000000000
--- a/ui/app/components/pages/confirm-deploy-contract/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './confirm-deploy-contract.container'
diff --git a/ui/app/components/pages/confirm-send-ether/confirm-send-ether.component.js b/ui/app/components/pages/confirm-send-ether/confirm-send-ether.component.js
deleted file mode 100644
index 442a478b8..000000000
--- a/ui/app/components/pages/confirm-send-ether/confirm-send-ether.component.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import ConfirmTransactionBase from '../confirm-transaction-base'
-import { SEND_ROUTE } from '../../../routes'
-
-export default class ConfirmSendEther extends Component {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- editTransaction: PropTypes.func,
- history: PropTypes.object,
- txParams: PropTypes.object,
- }
-
- handleEdit ({ txData }) {
- const { editTransaction, history } = this.props
- editTransaction(txData)
- history.push(SEND_ROUTE)
- }
-
- shouldHideData () {
- const { txParams = {} } = this.props
- return !txParams.data
- }
-
- render () {
- const hideData = this.shouldHideData()
-
- return (
- <ConfirmTransactionBase
- action={this.context.t('confirm')}
- hideData={hideData}
- onEdit={confirmTransactionData => this.handleEdit(confirmTransactionData)}
- />
- )
- }
-}
diff --git a/ui/app/components/pages/confirm-send-ether/confirm-send-ether.container.js b/ui/app/components/pages/confirm-send-ether/confirm-send-ether.container.js
deleted file mode 100644
index e48ef54a8..000000000
--- a/ui/app/components/pages/confirm-send-ether/confirm-send-ether.container.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { connect } from 'react-redux'
-import { compose } from 'recompose'
-import { withRouter } from 'react-router-dom'
-import { updateSend } from '../../../actions'
-import { clearConfirmTransaction } from '../../../ducks/confirm-transaction.duck'
-import ConfirmSendEther from './confirm-send-ether.component'
-
-const mapStateToProps = state => {
- const { confirmTransaction: { txData: { txParams } = {} } } = state
-
- return {
- txParams,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- editTransaction: txData => {
- const { id, txParams } = txData
- const {
- gas: gasLimit,
- gasPrice,
- to,
- value: amount,
- } = txParams
-
- dispatch(updateSend({
- gasLimit,
- gasPrice,
- gasTotal: null,
- to,
- amount,
- errors: { to: null, amount: null },
- editingTransactionId: id && id.toString(),
- }))
-
- dispatch(clearConfirmTransaction())
- },
- }
-}
-
-export default compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(ConfirmSendEther)
diff --git a/ui/app/components/pages/confirm-send-ether/index.js b/ui/app/components/pages/confirm-send-ether/index.js
deleted file mode 100644
index 2d5767c39..000000000
--- a/ui/app/components/pages/confirm-send-ether/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './confirm-send-ether.container'
diff --git a/ui/app/components/pages/confirm-send-token/confirm-send-token.component.js b/ui/app/components/pages/confirm-send-token/confirm-send-token.component.js
deleted file mode 100644
index cb39e3d7b..000000000
--- a/ui/app/components/pages/confirm-send-token/confirm-send-token.component.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import ConfirmTokenTransactionBase from '../confirm-token-transaction-base'
-import { SEND_ROUTE } from '../../../routes'
-
-export default class ConfirmSendToken extends Component {
- static propTypes = {
- history: PropTypes.object,
- editTransaction: PropTypes.func,
- tokenAmount: PropTypes.number,
- }
-
- handleEdit (confirmTransactionData) {
- const { editTransaction, history } = this.props
- editTransaction(confirmTransactionData)
- history.push(SEND_ROUTE)
- }
-
- render () {
- const { tokenAmount } = this.props
-
- return (
- <ConfirmTokenTransactionBase
- onEdit={confirmTransactionData => this.handleEdit(confirmTransactionData)}
- tokenAmount={tokenAmount}
- />
- )
- }
-}
diff --git a/ui/app/components/pages/confirm-send-token/confirm-send-token.container.js b/ui/app/components/pages/confirm-send-token/confirm-send-token.container.js
deleted file mode 100644
index d60911e59..000000000
--- a/ui/app/components/pages/confirm-send-token/confirm-send-token.container.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import { connect } from 'react-redux'
-import { compose } from 'recompose'
-import { withRouter } from 'react-router-dom'
-import ConfirmSendToken from './confirm-send-token.component'
-import { clearConfirmTransaction } from '../../../ducks/confirm-transaction.duck'
-import { setSelectedToken, updateSend, showSendTokenPage } from '../../../actions'
-import { conversionUtil } from '../../../conversion-util'
-import { sendTokenTokenAmountAndToAddressSelector } from '../../../selectors/confirm-transaction'
-
-const mapStateToProps = state => {
- const { tokenAmount } = sendTokenTokenAmountAndToAddressSelector(state)
-
- return {
- tokenAmount,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- editTransaction: ({ txData, tokenData, tokenProps }) => {
- const { txParams: { to: tokenAddress, gas: gasLimit, gasPrice } = {}, id } = txData
- const { params = [] } = tokenData
- const { value: to } = params[0] || {}
- const { value: tokenAmountInDec } = params[1] || {}
- const tokenAmountInHex = conversionUtil(tokenAmountInDec, {
- fromNumericBase: 'dec',
- toNumericBase: 'hex',
- })
- dispatch(setSelectedToken(tokenAddress))
- dispatch(updateSend({
- gasLimit,
- gasPrice,
- gasTotal: null,
- to,
- amount: tokenAmountInHex,
- errors: { to: null, amount: null },
- editingTransactionId: id && id.toString(),
- token: {
- ...tokenProps,
- address: tokenAddress,
- },
- }))
- dispatch(clearConfirmTransaction())
- dispatch(showSendTokenPage())
- },
- }
-}
-
-export default compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(ConfirmSendToken)
diff --git a/ui/app/components/pages/confirm-send-token/index.js b/ui/app/components/pages/confirm-send-token/index.js
deleted file mode 100644
index 409b6ef3d..000000000
--- a/ui/app/components/pages/confirm-send-token/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './confirm-send-token.container'
diff --git a/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js b/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js
deleted file mode 100644
index 7f1fb4e49..000000000
--- a/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import ConfirmTransactionBase from '../confirm-transaction-base'
-import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
-import {
- formatCurrency,
- convertTokenToFiat,
- addFiat,
- roundExponential,
-} from '../../../helpers/confirm-transaction/util'
-import { getWeiHexFromDecimalValue } from '../../../helpers/conversions.util'
-import { ETH, PRIMARY } from '../../../constants/common'
-
-export default class ConfirmTokenTransactionBase extends Component {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- tokenAddress: PropTypes.string,
- toAddress: PropTypes.string,
- tokenAmount: PropTypes.number,
- tokenSymbol: PropTypes.string,
- fiatTransactionTotal: PropTypes.string,
- ethTransactionTotal: PropTypes.string,
- contractExchangeRate: PropTypes.number,
- conversionRate: PropTypes.number,
- currentCurrency: PropTypes.string,
- }
-
- getFiatTransactionAmount () {
- const { tokenAmount, currentCurrency, conversionRate, contractExchangeRate } = this.props
-
- return convertTokenToFiat({
- value: tokenAmount,
- toCurrency: currentCurrency,
- conversionRate,
- contractExchangeRate,
- })
- }
-
- renderSubtitleComponent () {
- const { contractExchangeRate, tokenAmount } = this.props
-
- const decimalEthValue = (tokenAmount * contractExchangeRate) || 0
- const hexWeiValue = getWeiHexFromDecimalValue({
- value: decimalEthValue,
- fromCurrency: ETH,
- fromDenomination: ETH,
- })
-
- return typeof contractExchangeRate === 'undefined'
- ? (
- <span>
- { this.context.t('noConversionRateAvailable') }
- </span>
- ) : (
- <UserPreferencedCurrencyDisplay
- value={hexWeiValue}
- type={PRIMARY}
- showEthLogo
- hideLabel
- />
- )
- }
-
- renderPrimaryTotalTextOverride () {
- const { tokenAmount, tokenSymbol, ethTransactionTotal } = this.props
- const tokensText = `${tokenAmount} ${tokenSymbol}`
-
- return (
- <div>
- <span>{ `${tokensText} + ` }</span>
- <img
- src="/images/eth.svg"
- height="18"
- />
- <span>{ ethTransactionTotal }</span>
- </div>
- )
- }
-
- getSecondaryTotalTextOverride () {
- const { fiatTransactionTotal, currentCurrency, contractExchangeRate } = this.props
-
- if (typeof contractExchangeRate === 'undefined') {
- return formatCurrency(fiatTransactionTotal, currentCurrency)
- } else {
- const fiatTransactionAmount = this.getFiatTransactionAmount()
- const fiatTotal = addFiat(fiatTransactionAmount, fiatTransactionTotal)
- const roundedFiatTotal = roundExponential(fiatTotal)
- return formatCurrency(roundedFiatTotal, currentCurrency)
- }
- }
-
- render () {
- const {
- toAddress,
- tokenAddress,
- tokenSymbol,
- tokenAmount,
- ...restProps
- } = this.props
-
- const tokensText = `${tokenAmount} ${tokenSymbol}`
-
- return (
- <ConfirmTransactionBase
- toAddress={toAddress}
- identiconAddress={tokenAddress}
- title={tokensText}
- subtitleComponent={this.renderSubtitleComponent()}
- primaryTotalTextOverride={this.renderPrimaryTotalTextOverride()}
- secondaryTotalTextOverride={this.getSecondaryTotalTextOverride()}
- {...restProps}
- />
- )
- }
-}
diff --git a/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js b/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js
deleted file mode 100644
index be38acdb0..000000000
--- a/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { connect } from 'react-redux'
-import ConfirmTokenTransactionBase from './confirm-token-transaction-base.component'
-import {
- tokenAmountAndToAddressSelector,
- contractExchangeRateSelector,
-} from '../../../selectors/confirm-transaction'
-
-const mapStateToProps = (state, ownProps) => {
- const { tokenAmount: ownTokenAmount } = ownProps
- const { confirmTransaction, metamask: { currentCurrency, conversionRate } } = state
- const {
- txData: { txParams: { to: tokenAddress } = {} } = {},
- tokenProps: { tokenSymbol } = {},
- fiatTransactionTotal,
- ethTransactionTotal,
- } = confirmTransaction
-
- const { tokenAmount, toAddress } = tokenAmountAndToAddressSelector(state)
- const contractExchangeRate = contractExchangeRateSelector(state)
-
- return {
- toAddress,
- tokenAddress,
- tokenAmount: typeof ownTokenAmount !== 'undefined' ? ownTokenAmount : tokenAmount,
- tokenSymbol,
- currentCurrency,
- conversionRate,
- contractExchangeRate,
- fiatTransactionTotal,
- ethTransactionTotal,
- }
-}
-
-export default connect(mapStateToProps)(ConfirmTokenTransactionBase)
diff --git a/ui/app/components/pages/confirm-token-transaction-base/index.js b/ui/app/components/pages/confirm-token-transaction-base/index.js
deleted file mode 100644
index e15c5d56b..000000000
--- a/ui/app/components/pages/confirm-token-transaction-base/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default } from './confirm-token-transaction-base.container'
-export { default as ConfirmTokenTransactionBase } from './confirm-token-transaction-base.component'
diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
deleted file mode 100644
index 7d01aaffb..000000000
--- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
+++ /dev/null
@@ -1,412 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container'
-import { isBalanceSufficient } from '../../send/send.utils'
-import { DEFAULT_ROUTE } from '../../../routes'
-import {
- INSUFFICIENT_FUNDS_ERROR_KEY,
- TRANSACTION_ERROR_KEY,
-} from '../../../constants/error-keys'
-import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../../constants/transactions'
-import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
-import { PRIMARY, SECONDARY } from '../../../constants/common'
-
-export default class ConfirmTransactionBase extends Component {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- // react-router props
- match: PropTypes.object,
- history: PropTypes.object,
- // Redux props
- balance: PropTypes.string,
- cancelTransaction: PropTypes.func,
- cancelAllTransactions: PropTypes.func,
- clearConfirmTransaction: PropTypes.func,
- clearSend: PropTypes.func,
- conversionRate: PropTypes.number,
- currentCurrency: PropTypes.string,
- editTransaction: PropTypes.func,
- ethTransactionAmount: PropTypes.string,
- ethTransactionFee: PropTypes.string,
- ethTransactionTotal: PropTypes.string,
- fiatTransactionAmount: PropTypes.string,
- fiatTransactionFee: PropTypes.string,
- fiatTransactionTotal: PropTypes.string,
- fromAddress: PropTypes.string,
- fromName: PropTypes.string,
- hexTransactionAmount: PropTypes.string,
- hexTransactionFee: PropTypes.string,
- hexTransactionTotal: PropTypes.string,
- isTxReprice: PropTypes.bool,
- methodData: PropTypes.object,
- nonce: PropTypes.string,
- assetImage: PropTypes.string,
- sendTransaction: PropTypes.func,
- showCustomizeGasModal: PropTypes.func,
- showTransactionConfirmedModal: PropTypes.func,
- showRejectTransactionsConfirmationModal: PropTypes.func,
- toAddress: PropTypes.string,
- tokenData: PropTypes.object,
- tokenProps: PropTypes.object,
- toName: PropTypes.string,
- transactionStatus: PropTypes.string,
- txData: PropTypes.object,
- unapprovedTxCount: PropTypes.number,
- // Component props
- action: PropTypes.string,
- contentComponent: PropTypes.node,
- dataComponent: PropTypes.node,
- detailsComponent: PropTypes.node,
- errorKey: PropTypes.string,
- errorMessage: PropTypes.string,
- primaryTotalTextOverride: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
- secondaryTotalTextOverride: PropTypes.string,
- hideData: PropTypes.bool,
- hideDetails: PropTypes.bool,
- hideSubtitle: PropTypes.bool,
- identiconAddress: PropTypes.string,
- onCancel: PropTypes.func,
- onEdit: PropTypes.func,
- onEditGas: PropTypes.func,
- onSubmit: PropTypes.func,
- subtitle: PropTypes.string,
- subtitleComponent: PropTypes.node,
- summaryComponent: PropTypes.node,
- title: PropTypes.string,
- titleComponent: PropTypes.node,
- valid: PropTypes.bool,
- warning: PropTypes.string,
- }
-
- state = {
- submitting: false,
- submitError: null,
- }
-
- componentDidUpdate () {
- const {
- transactionStatus,
- showTransactionConfirmedModal,
- history,
- clearConfirmTransaction,
- } = this.props
-
- if (transactionStatus === DROPPED_STATUS || transactionStatus === CONFIRMED_STATUS) {
- showTransactionConfirmedModal({
- onSubmit: () => {
- clearConfirmTransaction()
- history.push(DEFAULT_ROUTE)
- },
- })
-
- return
- }
- }
-
- getErrorKey () {
- const {
- balance,
- conversionRate,
- hexTransactionFee,
- txData: {
- simulationFails,
- txParams: {
- value: amount,
- } = {},
- } = {},
- } = this.props
-
- const insufficientBalance = balance && !isBalanceSufficient({
- amount,
- gasTotal: hexTransactionFee || '0x0',
- balance,
- conversionRate,
- })
-
- if (insufficientBalance) {
- return {
- valid: false,
- errorKey: INSUFFICIENT_FUNDS_ERROR_KEY,
- }
- }
-
- if (simulationFails) {
- return {
- valid: true,
- errorKey: simulationFails.errorKey ? simulationFails.errorKey : TRANSACTION_ERROR_KEY,
- }
- }
-
- return {
- valid: true,
- }
- }
-
- handleEditGas () {
- const { onEditGas, showCustomizeGasModal } = this.props
-
- if (onEditGas) {
- onEditGas()
- } else {
- showCustomizeGasModal()
- }
- }
-
- renderDetails () {
- const {
- detailsComponent,
- primaryTotalTextOverride,
- secondaryTotalTextOverride,
- hexTransactionFee,
- hexTransactionTotal,
- hideDetails,
- } = this.props
-
- if (hideDetails) {
- return null
- }
-
- return (
- detailsComponent || (
- <div className="confirm-page-container-content__details">
- <div className="confirm-page-container-content__gas-fee">
- <ConfirmDetailRow
- label="Gas Fee"
- value={hexTransactionFee}
- headerText="Edit"
- headerTextClassName="confirm-detail-row__header-text--edit"
- onHeaderClick={() => this.handleEditGas()}
- />
- </div>
- <div>
- <ConfirmDetailRow
- label="Total"
- value={hexTransactionTotal}
- primaryText={primaryTotalTextOverride}
- secondaryText={secondaryTotalTextOverride}
- headerText="Amount + Gas Fee"
- headerTextClassName="confirm-detail-row__header-text--total"
- primaryValueTextColor="#2f9ae0"
- />
- </div>
- </div>
- )
- )
- }
-
- renderData () {
- const { t } = this.context
- const {
- txData: {
- txParams: {
- data,
- } = {},
- } = {},
- methodData: {
- name,
- params,
- } = {},
- hideData,
- dataComponent,
- } = this.props
-
- if (hideData) {
- return null
- }
-
- return dataComponent || (
- <div className="confirm-page-container-content__data">
- <div className="confirm-page-container-content__data-box-label">
- {`${t('functionType')}:`}
- <span className="confirm-page-container-content__function-type">
- { name || t('notFound') }
- </span>
- </div>
- {
- params && (
- <div className="confirm-page-container-content__data-box">
- <div className="confirm-page-container-content__data-field-label">
- { `${t('parameters')}:` }
- </div>
- <div>
- <pre>{ JSON.stringify(params, null, 2) }</pre>
- </div>
- </div>
- )
- }
- <div className="confirm-page-container-content__data-box-label">
- {`${t('hexData')}:`}
- </div>
- <div className="confirm-page-container-content__data-box">
- { data }
- </div>
- </div>
- )
- }
-
- handleEdit () {
- const { txData, tokenData, tokenProps, onEdit } = this.props
- onEdit({ txData, tokenData, tokenProps })
- }
-
- handleCancelAll () {
- const {
- cancelAllTransactions,
- clearConfirmTransaction,
- history,
- showRejectTransactionsConfirmationModal,
- unapprovedTxCount,
- } = this.props
-
- showRejectTransactionsConfirmationModal({
- unapprovedTxCount,
- async onSubmit () {
- await cancelAllTransactions()
- clearConfirmTransaction()
- history.push(DEFAULT_ROUTE)
- },
- })
- }
-
- handleCancel () {
- const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction } = this.props
-
- if (onCancel) {
- onCancel(txData)
- } else {
- cancelTransaction(txData)
- .then(() => {
- clearConfirmTransaction()
- history.push(DEFAULT_ROUTE)
- })
- }
- }
-
- handleSubmit () {
- const { sendTransaction, clearConfirmTransaction, txData, history, onSubmit } = this.props
- const { submitting } = this.state
-
- if (submitting) {
- return
- }
-
- this.setState({ submitting: true, submitError: null })
-
- if (onSubmit) {
- Promise.resolve(onSubmit(txData))
- .then(this.setState({ submitting: false }))
- } else {
- sendTransaction(txData)
- .then(() => {
- clearConfirmTransaction()
- this.setState({ submitting: false })
- history.push(DEFAULT_ROUTE)
- })
- .catch(error => {
- this.setState({ submitting: false, submitError: error.message })
- })
- }
- }
-
- renderTitleComponent () {
- const { title, titleComponent, hexTransactionAmount } = this.props
-
- // Title string passed in by props takes priority
- if (title) {
- return null
- }
-
- return titleComponent || (
- <UserPreferencedCurrencyDisplay
- value={hexTransactionAmount}
- type={PRIMARY}
- showEthLogo
- ethLogoHeight="26"
- hideLabel
- />
- )
- }
-
- renderSubtitleComponent () {
- const { subtitle, subtitleComponent, hexTransactionAmount } = this.props
-
- // Subtitle string passed in by props takes priority
- if (subtitle) {
- return null
- }
-
- return subtitleComponent || (
- <UserPreferencedCurrencyDisplay
- value={hexTransactionAmount}
- type={SECONDARY}
- showEthLogo
- hideLabel
- />
- )
- }
-
- render () {
- const {
- isTxReprice,
- fromName,
- fromAddress,
- toName,
- toAddress,
- methodData,
- valid: propsValid = true,
- errorMessage,
- errorKey: propsErrorKey,
- action,
- title,
- subtitle,
- hideSubtitle,
- identiconAddress,
- summaryComponent,
- contentComponent,
- onEdit,
- nonce,
- assetImage,
- warning,
- unapprovedTxCount,
- } = this.props
- const { submitting, submitError } = this.state
-
- const { name } = methodData
- const { valid, errorKey } = this.getErrorKey()
-
- return (
- <ConfirmPageContainer
- fromName={fromName}
- fromAddress={fromAddress}
- toName={toName}
- toAddress={toAddress}
- showEdit={onEdit && !isTxReprice}
- action={action || name || this.context.t('unknownFunction')}
- title={title}
- titleComponent={this.renderTitleComponent()}
- subtitle={subtitle}
- subtitleComponent={this.renderSubtitleComponent()}
- hideSubtitle={hideSubtitle}
- summaryComponent={summaryComponent}
- detailsComponent={this.renderDetails()}
- dataComponent={this.renderData()}
- contentComponent={contentComponent}
- nonce={nonce}
- unapprovedTxCount={unapprovedTxCount}
- assetImage={assetImage}
- identiconAddress={identiconAddress}
- errorMessage={errorMessage || submitError}
- errorKey={propsErrorKey || errorKey}
- warning={warning}
- disabled={!propsValid || !valid || submitting}
- onEdit={() => this.handleEdit()}
- onCancelAll={() => this.handleCancelAll()}
- onCancel={() => this.handleCancel()}
- onSubmit={() => this.handleSubmit()}
- />
- )
- }
-}
diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
deleted file mode 100644
index c366d5137..000000000
--- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
+++ /dev/null
@@ -1,203 +0,0 @@
-import { connect } from 'react-redux'
-import { compose } from 'recompose'
-import { withRouter } from 'react-router-dom'
-import R from 'ramda'
-import contractMap from 'eth-contract-metadata'
-import ConfirmTransactionBase from './confirm-transaction-base.component'
-import {
- clearConfirmTransaction,
- updateGasAndCalculate,
-} from '../../../ducks/confirm-transaction.duck'
-import { clearSend, cancelTx, cancelTxs, updateAndApproveTx, showModal } from '../../../actions'
-import {
- INSUFFICIENT_FUNDS_ERROR_KEY,
- GAS_LIMIT_TOO_LOW_ERROR_KEY,
-} from '../../../constants/error-keys'
-import { getHexGasTotal } from '../../../helpers/confirm-transaction/util'
-import { isBalanceSufficient } from '../../send/send.utils'
-import { conversionGreaterThan } from '../../../conversion-util'
-import { MIN_GAS_LIMIT_DEC } from '../../send/send.constants'
-import { addressSlicer, valuesFor } from '../../../util'
-
-const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
- return {
- ...acc,
- [base.toLowerCase()]: contractMap[base],
- }
-}, {})
-
-const mapStateToProps = (state, props) => {
- const { toAddress: propsToAddress } = props
- const { confirmTransaction, metamask } = state
- const {
- ethTransactionAmount,
- ethTransactionFee,
- ethTransactionTotal,
- fiatTransactionAmount,
- fiatTransactionFee,
- fiatTransactionTotal,
- hexTransactionAmount,
- hexTransactionFee,
- hexTransactionTotal,
- tokenData,
- methodData,
- txData,
- tokenProps,
- nonce,
- } = confirmTransaction
- const { txParams = {}, lastGasPrice, id: transactionId } = txData
- const { from: fromAddress, to: txParamsToAddress } = txParams
- const {
- conversionRate,
- identities,
- currentCurrency,
- accounts,
- selectedAddress,
- selectedAddressTxList,
- assetImages,
- network,
- unapprovedTxs,
- } = metamask
- const assetImage = assetImages[txParamsToAddress]
- const { balance } = accounts[selectedAddress]
- const { name: fromName } = identities[selectedAddress]
- const toAddress = propsToAddress || txParamsToAddress
- const toName = identities[toAddress]
- ? identities[toAddress].name
- : casedContractMap[toAddress] ? casedContractMap[toAddress].name : addressSlicer(toAddress)
-
- const isTxReprice = Boolean(lastGasPrice)
-
- const transaction = R.find(({ id }) => id === transactionId)(selectedAddressTxList)
- const transactionStatus = transaction ? transaction.status : ''
-
- const currentNetworkUnapprovedTxs = R.filter(
- ({ metamaskNetworkId }) => metamaskNetworkId === network,
- valuesFor(unapprovedTxs),
- )
- const unapprovedTxCount = currentNetworkUnapprovedTxs.length
-
- return {
- balance,
- fromAddress,
- fromName,
- toAddress,
- toName,
- ethTransactionAmount,
- ethTransactionFee,
- ethTransactionTotal,
- fiatTransactionAmount,
- fiatTransactionFee,
- fiatTransactionTotal,
- hexTransactionAmount,
- hexTransactionFee,
- hexTransactionTotal,
- txData,
- tokenData,
- methodData,
- tokenProps,
- isTxReprice,
- currentCurrency,
- conversionRate,
- transactionStatus,
- nonce,
- assetImage,
- unapprovedTxs,
- unapprovedTxCount,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
- clearSend: () => dispatch(clearSend()),
- showTransactionConfirmedModal: ({ onSubmit }) => {
- return dispatch(showModal({ name: 'TRANSACTION_CONFIRMED', onSubmit }))
- },
- showCustomizeGasModal: ({ txData, onSubmit, validate }) => {
- return dispatch(showModal({ name: 'CONFIRM_CUSTOMIZE_GAS', txData, onSubmit, validate }))
- },
- updateGasAndCalculate: ({ gasLimit, gasPrice }) => {
- return dispatch(updateGasAndCalculate({ gasLimit, gasPrice }))
- },
- showRejectTransactionsConfirmationModal: ({ onSubmit, unapprovedTxCount }) => {
- return dispatch(showModal({ name: 'REJECT_TRANSACTIONS', onSubmit, unapprovedTxCount }))
- },
- cancelTransaction: ({ id }) => dispatch(cancelTx({ id })),
- cancelAllTransactions: (txList) => dispatch(cancelTxs(txList)),
- sendTransaction: txData => dispatch(updateAndApproveTx(txData)),
- }
-}
-
-const getValidateEditGas = ({ balance, conversionRate, txData }) => {
- const { txParams: { value: amount } = {} } = txData
-
- return ({ gasLimit, gasPrice }) => {
- const gasTotal = getHexGasTotal({ gasLimit, gasPrice })
- const hasSufficientBalance = isBalanceSufficient({
- amount,
- gasTotal,
- balance,
- conversionRate,
- })
-
- if (!hasSufficientBalance) {
- return {
- valid: false,
- errorKey: INSUFFICIENT_FUNDS_ERROR_KEY,
- }
- }
-
- const gasLimitTooLow = gasLimit && conversionGreaterThan(
- {
- value: MIN_GAS_LIMIT_DEC,
- fromNumericBase: 'dec',
- conversionRate,
- },
- {
- value: gasLimit,
- fromNumericBase: 'hex',
- },
- )
-
- if (gasLimitTooLow) {
- return {
- valid: false,
- errorKey: GAS_LIMIT_TOO_LOW_ERROR_KEY,
- }
- }
-
- return {
- valid: true,
- }
- }
-}
-
-const mergeProps = (stateProps, dispatchProps, ownProps) => {
- const { balance, conversionRate, txData, unapprovedTxs } = stateProps
- const {
- cancelAllTransactions: dispatchCancelAllTransactions,
- showCustomizeGasModal: dispatchShowCustomizeGasModal,
- updateGasAndCalculate: dispatchUpdateGasAndCalculate,
- ...otherDispatchProps
- } = dispatchProps
-
- const validateEditGas = getValidateEditGas({ balance, conversionRate, txData })
-
- return {
- ...stateProps,
- ...otherDispatchProps,
- ...ownProps,
- showCustomizeGasModal: () => dispatchShowCustomizeGasModal({
- txData,
- onSubmit: txData => dispatchUpdateGasAndCalculate(txData),
- validate: validateEditGas,
- }),
- cancelAllTransactions: () => dispatchCancelAllTransactions(valuesFor(unapprovedTxs)),
- }
-}
-
-export default compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps, mergeProps)
-)(ConfirmTransactionBase)
diff --git a/ui/app/components/pages/confirm-transaction-base/index.js b/ui/app/components/pages/confirm-transaction-base/index.js
deleted file mode 100644
index 9996e9aeb..000000000
--- a/ui/app/components/pages/confirm-transaction-base/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './confirm-transaction-base.container'
diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
deleted file mode 100644
index 2c44b6094..000000000
--- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import { Redirect } from 'react-router-dom'
-import Loading from '../../loading-screen'
-import {
- CONFIRM_TRANSACTION_ROUTE,
- CONFIRM_DEPLOY_CONTRACT_PATH,
- CONFIRM_SEND_ETHER_PATH,
- CONFIRM_SEND_TOKEN_PATH,
- CONFIRM_APPROVE_PATH,
- CONFIRM_TRANSFER_FROM_PATH,
- CONFIRM_TOKEN_METHOD_PATH,
- SIGNATURE_REQUEST_PATH,
-} from '../../../routes'
-import { isConfirmDeployContract } from '../../../helpers/transactions.util'
-import {
- TOKEN_METHOD_TRANSFER,
- TOKEN_METHOD_APPROVE,
- TOKEN_METHOD_TRANSFER_FROM,
-} from '../../../constants/transactions'
-
-export default class ConfirmTransactionSwitch extends Component {
- static propTypes = {
- txData: PropTypes.object,
- methodData: PropTypes.object,
- fetchingData: PropTypes.bool,
- isEtherTransaction: PropTypes.bool,
- }
-
- redirectToTransaction () {
- const {
- txData,
- methodData: { name },
- fetchingData,
- isEtherTransaction,
- } = this.props
- const { id, txParams: { data } = {} } = txData
-
- if (isConfirmDeployContract(txData)) {
- const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}`
- return <Redirect to={{ pathname }} />
- }
-
- if (fetchingData) {
- return <Loading />
- }
-
- if (isEtherTransaction) {
- const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_ETHER_PATH}`
- return <Redirect to={{ pathname }} />
- }
-
- if (data) {
- const methodName = name && name.toLowerCase()
-
- switch (methodName) {
- case TOKEN_METHOD_TRANSFER: {
- const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_TOKEN_PATH}`
- return <Redirect to={{ pathname }} />
- }
- case TOKEN_METHOD_APPROVE: {
- const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_APPROVE_PATH}`
- return <Redirect to={{ pathname }} />
- }
- case TOKEN_METHOD_TRANSFER_FROM: {
- const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TRANSFER_FROM_PATH}`
- return <Redirect to={{ pathname }} />
- }
- default: {
- const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TOKEN_METHOD_PATH}`
- return <Redirect to={{ pathname }} />
- }
- }
- }
-
- const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_ETHER_PATH}`
- return <Redirect to={{ pathname }} />
- }
-
- render () {
- const { txData } = this.props
-
- if (txData.txParams) {
- return this.redirectToTransaction()
- } else if (txData.msgParams) {
- const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${SIGNATURE_REQUEST_PATH}`
- return <Redirect to={{ pathname }} />
- }
-
- return <Loading />
- }
-}
diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.container.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.container.js
deleted file mode 100644
index 7f2c36af2..000000000
--- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.container.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { connect } from 'react-redux'
-import ConfirmTransactionSwitch from './confirm-transaction-switch.component'
-
-const mapStateToProps = state => {
- const {
- confirmTransaction: {
- txData,
- methodData,
- fetchingData,
- toSmartContract,
- },
- } = state
-
- return {
- txData,
- methodData,
- fetchingData,
- isEtherTransaction: !toSmartContract,
- }
-}
-
-export default connect(mapStateToProps)(ConfirmTransactionSwitch)
diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.util.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.util.js
deleted file mode 100644
index 536aa5212..000000000
--- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.util.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export function isConfirmDeployContract (txData = {}) {
- const { txParams = {} } = txData
- return !txParams.to
-}
diff --git a/ui/app/components/pages/confirm-transaction-switch/index.js b/ui/app/components/pages/confirm-transaction-switch/index.js
deleted file mode 100644
index c288acb1a..000000000
--- a/ui/app/components/pages/confirm-transaction-switch/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import ConfirmTransactionSwitch from './confirm-transaction-switch.container'
-module.exports = ConfirmTransactionSwitch
diff --git a/ui/app/components/pages/confirm-transaction/confirm-transaction.component.js b/ui/app/components/pages/confirm-transaction/confirm-transaction.component.js
deleted file mode 100644
index 3ac656d73..000000000
--- a/ui/app/components/pages/confirm-transaction/confirm-transaction.component.js
+++ /dev/null
@@ -1,157 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import { Switch, Route } from 'react-router-dom'
-import Loading from '../../loading-screen'
-import ConfirmTransactionSwitch from '../confirm-transaction-switch'
-import ConfirmTransactionBase from '../confirm-transaction-base'
-import ConfirmSendEther from '../confirm-send-ether'
-import ConfirmSendToken from '../confirm-send-token'
-import ConfirmDeployContract from '../confirm-deploy-contract'
-import ConfirmApprove from '../confirm-approve'
-import ConfirmTokenTransactionBase from '../confirm-token-transaction-base'
-import ConfTx from '../../../conf-tx'
-import {
- DEFAULT_ROUTE,
- CONFIRM_TRANSACTION_ROUTE,
- CONFIRM_DEPLOY_CONTRACT_PATH,
- CONFIRM_SEND_ETHER_PATH,
- CONFIRM_SEND_TOKEN_PATH,
- CONFIRM_APPROVE_PATH,
- CONFIRM_TRANSFER_FROM_PATH,
- CONFIRM_TOKEN_METHOD_PATH,
- SIGNATURE_REQUEST_PATH,
-} from '../../../routes'
-
-export default class ConfirmTransaction extends Component {
- static propTypes = {
- history: PropTypes.object.isRequired,
- totalUnapprovedCount: PropTypes.number.isRequired,
- match: PropTypes.object,
- send: PropTypes.object,
- unconfirmedTransactions: PropTypes.array,
- setTransactionToConfirm: PropTypes.func,
- confirmTransaction: PropTypes.object,
- clearConfirmTransaction: PropTypes.func,
- }
-
- getParamsTransactionId () {
- const { match: { params: { id } = {} } } = this.props
- return id || null
- }
-
- componentDidMount () {
- const {
- totalUnapprovedCount = 0,
- send = {},
- history,
- confirmTransaction: { txData: { id: transactionId } = {} },
- } = this.props
-
- if (!totalUnapprovedCount && !send.to) {
- history.replace(DEFAULT_ROUTE)
- return
- }
-
- if (!transactionId) {
- this.setTransactionToConfirm()
- }
- }
-
- componentDidUpdate () {
- const {
- setTransactionToConfirm,
- confirmTransaction: { txData: { id: transactionId } = {} },
- clearConfirmTransaction,
- } = this.props
- const paramsTransactionId = this.getParamsTransactionId()
-
- if (paramsTransactionId && transactionId && paramsTransactionId !== transactionId + '') {
- clearConfirmTransaction()
- setTransactionToConfirm(paramsTransactionId)
- return
- }
-
- if (!transactionId) {
- this.setTransactionToConfirm()
- }
- }
-
- setTransactionToConfirm () {
- const {
- history,
- unconfirmedTransactions,
- setTransactionToConfirm,
- } = this.props
- const paramsTransactionId = this.getParamsTransactionId()
-
- if (paramsTransactionId) {
- // Check to make sure params ID is valid
- const tx = unconfirmedTransactions.find(({ id }) => id + '' === paramsTransactionId)
-
- if (!tx) {
- history.replace(DEFAULT_ROUTE)
- } else {
- setTransactionToConfirm(paramsTransactionId)
- }
- } else if (unconfirmedTransactions.length) {
- const totalUnconfirmed = unconfirmedTransactions.length
- const transaction = unconfirmedTransactions[totalUnconfirmed - 1]
- const { id: transactionId, loadingDefaults } = transaction
-
- if (!loadingDefaults) {
- setTransactionToConfirm(transactionId)
- }
- }
- }
-
- render () {
- const { confirmTransaction: { txData: { id } } = {} } = this.props
- const paramsTransactionId = this.getParamsTransactionId()
-
- // Show routes when state.confirmTransaction has been set and when either the ID in the params
- // isn't specified or is specified and matches the ID in state.confirmTransaction in order to
- // support URLs of /confirm-transaction or /confirm-transaction/<transactionId>
- return id && (!paramsTransactionId || paramsTransactionId === id + '')
- ? (
- <Switch>
- <Route
- exact
- path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_DEPLOY_CONTRACT_PATH}`}
- component={ConfirmDeployContract}
- />
- <Route
- exact
- path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_TOKEN_METHOD_PATH}`}
- component={ConfirmTransactionBase}
- />
- <Route
- exact
- path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SEND_ETHER_PATH}`}
- component={ConfirmSendEther}
- />
- <Route
- exact
- path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SEND_TOKEN_PATH}`}
- component={ConfirmSendToken}
- />
- <Route
- exact
- path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_APPROVE_PATH}`}
- component={ConfirmApprove}
- />
- <Route
- exact
- path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_TRANSFER_FROM_PATH}`}
- component={ConfirmTokenTransactionBase}
- />
- <Route
- exact
- path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${SIGNATURE_REQUEST_PATH}`}
- component={ConfTx}
- />
- <Route path="*" component={ConfirmTransactionSwitch} />
- </Switch>
- )
- : <Loading />
- }
-}
diff --git a/ui/app/components/pages/confirm-transaction/confirm-transaction.container.js b/ui/app/components/pages/confirm-transaction/confirm-transaction.container.js
deleted file mode 100644
index 1bc2f1efb..000000000
--- a/ui/app/components/pages/confirm-transaction/confirm-transaction.container.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { connect } from 'react-redux'
-import { compose } from 'recompose'
-import { withRouter } from 'react-router-dom'
-import {
- setTransactionToConfirm,
- clearConfirmTransaction,
-} from '../../../ducks/confirm-transaction.duck'
-import ConfirmTransaction from './confirm-transaction.component'
-import { getTotalUnapprovedCount } from '../../../selectors'
-import { unconfirmedTransactionsListSelector } from '../../../selectors/confirm-transaction'
-
-const mapStateToProps = state => {
- const { metamask: { send }, confirmTransaction } = state
-
- return {
- totalUnapprovedCount: getTotalUnapprovedCount(state),
- send,
- confirmTransaction,
- unconfirmedTransactions: unconfirmedTransactionsListSelector(state),
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- setTransactionToConfirm: transactionId => dispatch(setTransactionToConfirm(transactionId)),
- clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
- }
-}
-
-export default compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps),
-)(ConfirmTransaction)
diff --git a/ui/app/components/pages/confirm-transaction/index.js b/ui/app/components/pages/confirm-transaction/index.js
deleted file mode 100644
index 4bf42d85c..000000000
--- a/ui/app/components/pages/confirm-transaction/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import ConfirmTransaction from './confirm-transaction.container'
-module.exports = ConfirmTransaction
diff --git a/ui/app/components/pages/create-account/connect-hardware/account-list.js b/ui/app/components/pages/create-account/connect-hardware/account-list.js
deleted file mode 100644
index 2767b2e1f..000000000
--- a/ui/app/components/pages/create-account/connect-hardware/account-list.js
+++ /dev/null
@@ -1,205 +0,0 @@
-const { Component } = require('react')
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const genAccountLink = require('../../../../../lib/account-link.js')
-const Select = require('react-select').default
-import Button from '../../../button'
-
-class AccountList extends Component {
- constructor (props, context) {
- super(props)
- }
-
- getHdPaths () {
- return [
- {
- label: `Ledger Live`,
- value: `m/44'/60'/0'/0/0`,
- },
- {
- label: `Legacy (MEW / MyCrypto)`,
- value: `m/44'/60'/0'`,
- },
- ]
- }
-
- goToNextPage = () => {
- // If we have < 5 accounts, it's restricted by BIP-44
- if (this.props.accounts.length === 5) {
- this.props.getPage(this.props.device, 1, this.props.selectedPath)
- } else {
- this.props.onAccountRestriction()
- }
- }
-
- goToPreviousPage = () => {
- this.props.getPage(this.props.device, -1, this.props.selectedPath)
- }
-
- renderHdPathSelector () {
- const { onPathChange, selectedPath } = this.props
-
- const options = this.getHdPaths()
- return h('div', [
- h('h3.hw-connect__hdPath__title', {}, this.context.t('selectHdPath')),
- h('p.hw-connect__msg', {}, this.context.t('selectPathHelp')),
- h('div.hw-connect__hdPath', [
- h(Select, {
- className: 'hw-connect__hdPath__select',
- name: 'hd-path-select',
- clearable: false,
- value: selectedPath,
- options,
- onChange: (opt) => {
- onPathChange(opt.value)
- },
- }),
- ]),
- ])
- }
-
- capitalizeDevice (device) {
- return device.slice(0, 1).toUpperCase() + device.slice(1)
- }
-
- renderHeader () {
- const { device } = this.props
- return (
- h('div.hw-connect', [
-
- h('h3.hw-connect__unlock-title', {}, `${this.context.t('unlock')} ${this.capitalizeDevice(device)}`),
-
- device.toLowerCase() === 'ledger' ? this.renderHdPathSelector() : null,
-
- h('h3.hw-connect__hdPath__title', {}, this.context.t('selectAnAccount')),
- h('p.hw-connect__msg', {}, this.context.t('selectAnAccountHelp')),
- ])
- )
- }
-
- renderAccounts () {
- return h('div.hw-account-list', [
- this.props.accounts.map((a, i) => {
-
- return h('div.hw-account-list__item', { key: a.address }, [
- h('div.hw-account-list__item__radio', [
- h('input', {
- type: 'radio',
- name: 'selectedAccount',
- id: `address-${i}`,
- value: a.index,
- onChange: (e) => this.props.onAccountChange(e.target.value),
- checked: this.props.selectedAccount === a.index.toString(),
- }),
- h(
- 'label.hw-account-list__item__label',
- {
- htmlFor: `address-${i}`,
- },
- [
- h('span.hw-account-list__item__index', a.index + 1),
- `${a.address.slice(0, 4)}...${a.address.slice(-4)}`,
- h('span.hw-account-list__item__balance', `${a.balance}`),
- ]),
- ]),
- h(
- 'a.hw-account-list__item__link',
- {
- href: genAccountLink(a.address, this.props.network),
- target: '_blank',
- title: this.context.t('etherscanView'),
- },
- h('img', { src: 'images/popout.svg' })
- ),
- ])
- }),
- ])
- }
-
- renderPagination () {
- return h('div.hw-list-pagination', [
- h(
- 'button.hw-list-pagination__button',
- {
- onClick: this.goToPreviousPage,
- },
- `< ${this.context.t('prev')}`
- ),
-
- h(
- 'button.hw-list-pagination__button',
- {
- onClick: this.goToNextPage,
- },
- `${this.context.t('next')} >`
- ),
- ])
- }
-
- renderButtons () {
- const disabled = this.props.selectedAccount === null
- const buttonProps = {}
- if (disabled) {
- buttonProps.disabled = true
- }
-
- return h('div.new-account-connect-form__buttons', {}, [
- h(Button, {
- type: 'default',
- large: true,
- className: 'new-account-connect-form__button',
- onClick: this.props.onCancel.bind(this),
- }, [this.context.t('cancel')]),
-
- h(Button, {
- type: 'primary',
- large: true,
- className: 'new-account-connect-form__button unlock',
- disabled,
- onClick: this.props.onUnlockAccount.bind(this, this.props.device),
- }, [this.context.t('unlock')]),
- ])
- }
-
- renderForgetDevice () {
- return h('div.hw-forget-device-container', {}, [
- h('a', {
- onClick: this.props.onForgetDevice.bind(this, this.props.device),
- }, this.context.t('forgetDevice')),
- ])
- }
-
- render () {
- return h('div.new-account-connect-form.account-list', {}, [
- this.renderHeader(),
- this.renderAccounts(),
- this.renderPagination(),
- this.renderButtons(),
- this.renderForgetDevice(),
- ])
- }
-
-}
-
-
-AccountList.propTypes = {
- onPathChange: PropTypes.func.isRequired,
- selectedPath: PropTypes.string.isRequired,
- device: PropTypes.string.isRequired,
- accounts: PropTypes.array.isRequired,
- onAccountChange: PropTypes.func.isRequired,
- onForgetDevice: PropTypes.func.isRequired,
- getPage: PropTypes.func.isRequired,
- network: PropTypes.string,
- selectedAccount: PropTypes.string,
- history: PropTypes.object,
- onUnlockAccount: PropTypes.func,
- onCancel: PropTypes.func,
- onAccountRestriction: PropTypes.func,
-}
-
-AccountList.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = AccountList
diff --git a/ui/app/components/pages/create-account/connect-hardware/connect-screen.js b/ui/app/components/pages/create-account/connect-hardware/connect-screen.js
deleted file mode 100644
index d3abf3119..000000000
--- a/ui/app/components/pages/create-account/connect-hardware/connect-screen.js
+++ /dev/null
@@ -1,197 +0,0 @@
-const { Component } = require('react')
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-import Button from '../../../button'
-
-class ConnectScreen extends Component {
- constructor (props, context) {
- super(props)
- this.state = {
- selectedDevice: null,
- }
- }
-
- connect = () => {
- if (this.state.selectedDevice) {
- this.props.connectToHardwareWallet(this.state.selectedDevice)
- }
- return null
- }
-
- renderConnectToTrezorButton () {
- return h(
- `button.hw-connect__btn${this.state.selectedDevice === 'trezor' ? '.selected' : ''}`,
- { onClick: _ => this.setState({selectedDevice: 'trezor'}) },
- h('img.hw-connect__btn__img', {
- src: 'images/trezor-logo.svg',
- })
- )
- }
-
- renderConnectToLedgerButton () {
- return h(
- `button.hw-connect__btn${this.state.selectedDevice === 'ledger' ? '.selected' : ''}`,
- { onClick: _ => this.setState({selectedDevice: 'ledger'}) },
- h('img.hw-connect__btn__img', {
- src: 'images/ledger-logo.svg',
- })
- )
- }
-
- renderButtons () {
- return (
- h('div', {}, [
- h('div.hw-connect__btn-wrapper', {}, [
- this.renderConnectToLedgerButton(),
- this.renderConnectToTrezorButton(),
- ]),
- h(
- `button.hw-connect__connect-btn${!this.state.selectedDevice ? '.disabled' : ''}`,
- { onClick: this.connect },
- this.context.t('connect')
- ),
- ])
- )
- }
-
- renderUnsupportedBrowser () {
- return (
- h('div.new-account-connect-form.unsupported-browser', {}, [
- h('div.hw-connect', [
- h('h3.hw-connect__title', {}, this.context.t('browserNotSupported')),
- h('p.hw-connect__msg', {}, this.context.t('chromeRequiredForHardwareWallets')),
- ]),
- h(Button, {
- type: 'primary',
- large: true,
- onClick: () => global.platform.openWindow({
- url: 'https://google.com/chrome',
- }),
- },
- this.context.t('downloadGoogleChrome')
- ),
- ])
- )
- }
-
- renderHeader () {
- return (
- h('div.hw-connect__header', {}, [
- h('h3.hw-connect__header__title', {}, this.context.t(`hardwareWallets`)),
- h('p.hw-connect__header__msg', {}, this.context.t(`hardwareWalletsMsg`)),
- ])
- )
- }
-
- getAffiliateLinks () {
- const links = {
- trezor: `<a class='hw-connect__get-hw__link' href='https://shop.trezor.io/?a=metamask' target='_blank'>Trezor</a>`,
- ledger: `<a class='hw-connect__get-hw__link' href='https://www.ledger.com/products/ledger-nano-s?r=17c4991a03fa&tracker=MY_TRACKER' target='_blank'>Ledger</a>`,
- }
-
- const text = this.context.t('orderOneHere')
- const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger)
-
- return h('div.hw-connect__get-hw__msg', { dangerouslySetInnerHTML: {__html: response }})
- }
-
- renderTrezorAffiliateLink () {
- return h('div.hw-connect__get-hw', {}, [
- h('p.hw-connect__get-hw__msg', {}, this.context.t(`dontHaveAHardwareWallet`)),
- this.getAffiliateLinks(),
- ])
- }
-
-
- scrollToTutorial = (e) => {
- if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'})
- }
-
- renderLearnMore () {
- return (
- h('p.hw-connect__learn-more', {
- onClick: this.scrollToTutorial,
- }, [
- this.context.t('learnMore'),
- h('img.hw-connect__learn-more__arrow', { src: 'images/caret-right.svg'}),
- ])
- )
- }
-
- renderTutorialSteps () {
- const steps = [
- {
- asset: 'hardware-wallet-step-1',
- dimensions: {width: '225px', height: '75px'},
- },
- {
- asset: 'hardware-wallet-step-2',
- dimensions: {width: '300px', height: '100px'},
- },
- {
- asset: 'hardware-wallet-step-3',
- dimensions: {width: '120px', height: '90px'},
- },
- ]
-
- return h('.hw-tutorial', {
- ref: node => { this.referenceNode = node },
- },
- steps.map((step, i) => (
- h('div.hw-connect', {}, [
- h('h3.hw-connect__title', {}, this.context.t(`step${i + 1}HardwareWallet`)),
- h('p.hw-connect__msg', {}, this.context.t(`step${i + 1}HardwareWalletMsg`)),
- h('img.hw-connect__step-asset', { src: `images/${step.asset}.svg`, ...step.dimensions }),
- ])
- ))
- )
- }
-
- renderFooter () {
- return (
- h('div.hw-connect__footer', {}, [
- h('h3.hw-connect__footer__title', {}, this.context.t(`readyToConnect`)),
- this.renderButtons(),
- h('p.hw-connect__footer__msg', {}, [
- this.context.t(`havingTroubleConnecting`),
- h('a.hw-connect__footer__link', {
- href: 'https://support.metamask.io/',
- target: '_blank',
- }, this.context.t('getHelp')),
- ]),
- ])
- )
- }
-
- renderConnectScreen () {
- return (
- h('div.new-account-connect-form', {}, [
- this.renderHeader(),
- this.renderButtons(),
- this.renderTrezorAffiliateLink(),
- this.renderLearnMore(),
- this.renderTutorialSteps(),
- this.renderFooter(),
- ])
- )
- }
-
- render () {
- if (this.props.browserSupported) {
- return this.renderConnectScreen()
- }
- return this.renderUnsupportedBrowser()
- }
-}
-
-ConnectScreen.propTypes = {
- connectToHardwareWallet: PropTypes.func.isRequired,
- browserSupported: PropTypes.bool.isRequired,
-}
-
-ConnectScreen.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = ConnectScreen
-
diff --git a/ui/app/components/pages/create-account/connect-hardware/index.js b/ui/app/components/pages/create-account/connect-hardware/index.js
deleted file mode 100644
index 547df5223..000000000
--- a/ui/app/components/pages/create-account/connect-hardware/index.js
+++ /dev/null
@@ -1,274 +0,0 @@
-const { Component } = require('react')
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const connect = require('react-redux').connect
-const actions = require('../../../../actions')
-const ConnectScreen = require('./connect-screen')
-const AccountList = require('./account-list')
-const { DEFAULT_ROUTE } = require('../../../../routes')
-const { formatBalance } = require('../../../../util')
-const { getPlatform } = require('../../../../../../app/scripts/lib/util')
-const { PLATFORM_FIREFOX } = require('../../../../../../app/scripts/lib/enums')
-
-class ConnectHardwareForm extends Component {
- constructor (props, context) {
- super(props)
- this.state = {
- error: null,
- selectedAccount: null,
- accounts: [],
- browserSupported: true,
- unlocked: false,
- device: null,
- }
- }
-
- componentWillReceiveProps (nextProps) {
- const { accounts } = nextProps
- const newAccounts = this.state.accounts.map(a => {
- const normalizedAddress = a.address.toLowerCase()
- const balanceValue = accounts[normalizedAddress] && accounts[normalizedAddress].balance || null
- a.balance = balanceValue ? formatBalance(balanceValue, 6) : '...'
- return a
- })
- this.setState({accounts: newAccounts})
- }
-
-
- componentDidMount () {
- this.checkIfUnlocked()
- }
-
- async checkIfUnlocked () {
- ['trezor', 'ledger'].forEach(async device => {
- const unlocked = await this.props.checkHardwareStatus(device, this.props.defaultHdPaths[device])
- if (unlocked) {
- this.setState({unlocked: true})
- this.getPage(device, 0, this.props.defaultHdPaths[device])
- }
- })
- }
-
- connectToHardwareWallet = (device) => {
- // None of the hardware wallets are supported
- // At least for now
- if (getPlatform() === PLATFORM_FIREFOX) {
- this.setState({ browserSupported: false, error: null})
- return null
- }
-
- if (this.state.accounts.length) {
- return null
- }
-
- // Default values
- this.getPage(device, 0, this.props.defaultHdPaths[device])
- }
-
- onPathChange = (path) => {
- this.props.setHardwareWalletDefaultHdPath({device: this.state.device, path})
- this.getPage(this.state.device, 0, path)
- }
-
- onAccountChange = (account) => {
- this.setState({selectedAccount: account.toString(), error: null})
- }
-
- onAccountRestriction = () => {
- this.setState({error: this.context.t('ledgerAccountRestriction') })
- }
-
- showTemporaryAlert () {
- this.props.showAlert(this.context.t('hardwareWalletConnected'))
- // Autohide the alert after 5 seconds
- setTimeout(_ => {
- this.props.hideAlert()
- }, 5000)
- }
-
- getPage = (device, page, hdPath) => {
- this.props
- .connectHardware(device, page, hdPath)
- .then(accounts => {
- if (accounts.length) {
-
- // If we just loaded the accounts for the first time
- // (device previously locked) show the global alert
- if (this.state.accounts.length === 0 && !this.state.unlocked) {
- this.showTemporaryAlert()
- }
-
- const newState = { unlocked: true, device, error: null }
- // Default to the first account
- if (this.state.selectedAccount === null) {
- accounts.forEach((a, i) => {
- if (a.address.toLowerCase() === this.props.address) {
- newState.selectedAccount = a.index.toString()
- }
- })
- // If the page doesn't contain the selected account, let's deselect it
- } else if (!accounts.filter(a => a.index.toString() === this.state.selectedAccount).length) {
- newState.selectedAccount = null
- }
-
-
- // Map accounts with balances
- newState.accounts = accounts.map(account => {
- const normalizedAddress = account.address.toLowerCase()
- const balanceValue = this.props.accounts[normalizedAddress] && this.props.accounts[normalizedAddress].balance || null
- account.balance = balanceValue ? formatBalance(balanceValue, 6) : '...'
- return account
- })
-
- this.setState(newState)
- }
- })
- .catch(e => {
- if (e === 'Window blocked') {
- this.setState({ browserSupported: false, error: null})
- } else if (e !== 'Window closed') {
- this.setState({ error: e.toString() })
- }
- })
- }
-
- onForgetDevice = (device) => {
- this.props.forgetDevice(device)
- .then(_ => {
- this.setState({
- error: null,
- selectedAccount: null,
- accounts: [],
- unlocked: false,
- })
- }).catch(e => {
- this.setState({ error: e.toString() })
- })
- }
-
- onUnlockAccount = (device) => {
-
- if (this.state.selectedAccount === null) {
- this.setState({ error: this.context.t('accountSelectionRequired') })
- }
-
- this.props.unlockHardwareWalletAccount(this.state.selectedAccount, device)
- .then(_ => {
- this.props.history.push(DEFAULT_ROUTE)
- }).catch(e => {
- this.setState({ error: e.toString() })
- })
- }
-
- onCancel = () => {
- this.props.history.push(DEFAULT_ROUTE)
- }
-
- renderError () {
- return this.state.error
- ? h('span.error', { style: { margin: '20px 20px 10px', display: 'block', textAlign: 'center' } }, this.state.error)
- : null
- }
-
- renderContent () {
- if (!this.state.accounts.length) {
- return h(ConnectScreen, {
- connectToHardwareWallet: this.connectToHardwareWallet,
- browserSupported: this.state.browserSupported,
- })
- }
-
- return h(AccountList, {
- onPathChange: this.onPathChange,
- selectedPath: this.props.defaultHdPaths[this.state.device],
- device: this.state.device,
- accounts: this.state.accounts,
- selectedAccount: this.state.selectedAccount,
- onAccountChange: this.onAccountChange,
- network: this.props.network,
- getPage: this.getPage,
- history: this.props.history,
- onUnlockAccount: this.onUnlockAccount,
- onForgetDevice: this.onForgetDevice,
- onCancel: this.onCancel,
- onAccountRestriction: this.onAccountRestriction,
- })
- }
-
- render () {
- return h('div', [
- this.renderError(),
- this.renderContent(),
- ])
- }
-}
-
-ConnectHardwareForm.propTypes = {
- hideModal: PropTypes.func,
- showImportPage: PropTypes.func,
- showConnectPage: PropTypes.func,
- connectHardware: PropTypes.func,
- checkHardwareStatus: PropTypes.func,
- forgetDevice: PropTypes.func,
- showAlert: PropTypes.func,
- hideAlert: PropTypes.func,
- unlockHardwareWalletAccount: PropTypes.func,
- setHardwareWalletDefaultHdPath: PropTypes.func,
- numberOfExistingAccounts: PropTypes.number,
- history: PropTypes.object,
- t: PropTypes.func,
- network: PropTypes.string,
- accounts: PropTypes.object,
- address: PropTypes.string,
- defaultHdPaths: PropTypes.object,
-}
-
-const mapStateToProps = state => {
- const {
- metamask: { network, selectedAddress, identities = {}, accounts = [] },
- } = state
- const numberOfExistingAccounts = Object.keys(identities).length
- const {
- appState: { defaultHdPaths },
- } = state
-
- return {
- network,
- accounts,
- address: selectedAddress,
- numberOfExistingAccounts,
- defaultHdPaths,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- setHardwareWalletDefaultHdPath: ({device, path}) => {
- return dispatch(actions.setHardwareWalletDefaultHdPath({device, path}))
- },
- connectHardware: (deviceName, page, hdPath) => {
- return dispatch(actions.connectHardware(deviceName, page, hdPath))
- },
- checkHardwareStatus: (deviceName, hdPath) => {
- return dispatch(actions.checkHardwareStatus(deviceName, hdPath))
- },
- forgetDevice: (deviceName) => {
- return dispatch(actions.forgetDevice(deviceName))
- },
- unlockHardwareWalletAccount: (index, deviceName, hdPath) => {
- return dispatch(actions.unlockHardwareWalletAccount(index, deviceName, hdPath))
- },
- showImportPage: () => dispatch(actions.showImportPage()),
- showConnectPage: () => dispatch(actions.showConnectPage()),
- showAlert: (msg) => dispatch(actions.showAlert(msg)),
- hideAlert: () => dispatch(actions.hideAlert()),
- }
-}
-
-ConnectHardwareForm.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect(mapStateToProps, mapDispatchToProps)(
- ConnectHardwareForm
-)
diff --git a/ui/app/components/pages/create-account/import-account/index.js b/ui/app/components/pages/create-account/import-account/index.js
deleted file mode 100644
index 48d8f8838..000000000
--- a/ui/app/components/pages/create-account/import-account/index.js
+++ /dev/null
@@ -1,96 +0,0 @@
-const inherits = require('util').inherits
-const Component = require('react').Component
-const h = require('react-hyperscript')
-const PropTypes = require('prop-types')
-const connect = require('react-redux').connect
-import Select from 'react-select'
-
-// Subviews
-const JsonImportView = require('./json.js')
-const PrivateKeyImportView = require('./private-key.js')
-
-
-AccountImportSubview.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect()(AccountImportSubview)
-
-
-inherits(AccountImportSubview, Component)
-function AccountImportSubview () {
- Component.call(this)
-}
-
-AccountImportSubview.prototype.getMenuItemTexts = function () {
- return [
- this.context.t('privateKey'),
- this.context.t('jsonFile'),
- ]
-}
-
-AccountImportSubview.prototype.render = function () {
- const state = this.state || {}
- const menuItems = this.getMenuItemTexts()
- const { type } = state
-
- return (
- h('div.new-account-import-form', [
-
- h('.new-account-import-disclaimer', [
- h('span', this.context.t('importAccountMsg')),
- h('span', {
- style: {
- cursor: 'pointer',
- textDecoration: 'underline',
- },
- onClick: () => {
- global.platform.openWindow({
- url: 'https://metamask.zendesk.com/hc/en-us/articles/360015289932',
- })
- },
- }, this.context.t('here')),
- ]),
-
- h('div.new-account-import-form__select-section', [
-
- h('div.new-account-import-form__select-label', this.context.t('selectType')),
-
- h(Select, {
- className: 'new-account-import-form__select',
- name: 'import-type-select',
- clearable: false,
- value: type || menuItems[0],
- options: menuItems.map((type) => {
- return {
- value: type,
- label: type,
- }
- }),
- onChange: (opt) => {
- this.setState({ type: opt.value })
- },
- }),
-
- ]),
-
- this.renderImportView(),
- ])
- )
-}
-
-AccountImportSubview.prototype.renderImportView = function () {
- const state = this.state || {}
- const { type } = state
- const menuItems = this.getMenuItemTexts()
- const current = type || menuItems[0]
-
- switch (current) {
- case this.context.t('privateKey'):
- return h(PrivateKeyImportView)
- case this.context.t('jsonFile'):
- return h(JsonImportView)
- default:
- return h(JsonImportView)
- }
-}
diff --git a/ui/app/components/pages/create-account/import-account/json.js b/ui/app/components/pages/create-account/import-account/json.js
deleted file mode 100644
index 90279bbbd..000000000
--- a/ui/app/components/pages/create-account/import-account/json.js
+++ /dev/null
@@ -1,159 +0,0 @@
-const Component = require('react').Component
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const { withRouter } = require('react-router-dom')
-const { compose } = require('recompose')
-const connect = require('react-redux').connect
-const actions = require('../../../../actions')
-const FileInput = require('react-simple-file-input').default
-const { DEFAULT_ROUTE } = require('../../../../routes')
-const HELP_LINK = 'https://support.metamask.io/kb/article/7-importing-accounts'
-import Button from '../../../button'
-
-class JsonImportSubview extends Component {
- constructor (props) {
- super(props)
-
- this.state = {
- file: null,
- fileContents: '',
- }
- }
-
- render () {
- const { error } = this.props
-
- return (
- h('div.new-account-import-form__json', [
-
- h('p', this.context.t('usedByClients')),
- h('a.warning', {
- href: HELP_LINK,
- target: '_blank',
- }, this.context.t('fileImportFail')),
-
- h(FileInput, {
- readAs: 'text',
- onLoad: this.onLoad.bind(this),
- style: {
- margin: '20px 0px 12px 34%',
- fontSize: '15px',
- display: 'flex',
- justifyContent: 'center',
- },
- }),
-
- h('input.new-account-import-form__input-password', {
- type: 'password',
- placeholder: this.context.t('enterPassword'),
- id: 'json-password-box',
- onKeyPress: this.createKeyringOnEnter.bind(this),
- }),
-
- h('div.new-account-create-form__buttons', {}, [
-
- h(Button, {
- type: 'default',
- large: true,
- className: 'new-account-create-form__button',
- onClick: () => this.props.history.push(DEFAULT_ROUTE),
- }, [this.context.t('cancel')]),
-
- h(Button, {
- type: 'primary',
- large: true,
- className: 'new-account-create-form__button',
- onClick: () => this.createNewKeychain(),
- }, [this.context.t('import')]),
-
- ]),
-
- error ? h('span.error', error) : null,
- ])
- )
- }
-
- onLoad (event, file) {
- this.setState({file: file, fileContents: event.target.result})
- }
-
- createKeyringOnEnter (event) {
- if (event.key === 'Enter') {
- event.preventDefault()
- this.createNewKeychain()
- }
- }
-
- createNewKeychain () {
- const { firstAddress, displayWarning, importNewJsonAccount, setSelectedAddress, history } = this.props
- const state = this.state
-
- if (!state) {
- const message = this.context.t('validFileImport')
- return displayWarning(message)
- }
-
- const { fileContents } = state
-
- if (!fileContents) {
- const message = this.context.t('needImportFile')
- return displayWarning(message)
- }
-
- const passwordInput = document.getElementById('json-password-box')
- const password = passwordInput.value
-
- if (!password) {
- const message = this.context.t('needImportPassword')
- return displayWarning(message)
- }
-
- importNewJsonAccount([ fileContents, password ])
- .then(({ selectedAddress }) => {
- if (selectedAddress) {
- history.push(DEFAULT_ROUTE)
- displayWarning(null)
- } else {
- displayWarning('Error importing account.')
- setSelectedAddress(firstAddress)
- }
- })
- .catch(err => err && displayWarning(err.message || err))
- }
-}
-
-JsonImportSubview.propTypes = {
- error: PropTypes.string,
- goHome: PropTypes.func,
- displayWarning: PropTypes.func,
- firstAddress: PropTypes.string,
- importNewJsonAccount: PropTypes.func,
- history: PropTypes.object,
- setSelectedAddress: PropTypes.func,
- t: PropTypes.func,
-}
-
-const mapStateToProps = state => {
- return {
- error: state.appState.warning,
- firstAddress: Object.keys(state.metamask.accounts)[0],
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- goHome: () => dispatch(actions.goHome()),
- displayWarning: warning => dispatch(actions.displayWarning(warning)),
- importNewJsonAccount: options => dispatch(actions.importNewAccount('JSON File', options)),
- setSelectedAddress: (address) => dispatch(actions.setSelectedAddress(address)),
- }
-}
-
-JsonImportSubview.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(JsonImportSubview)
diff --git a/ui/app/components/pages/create-account/import-account/private-key.js b/ui/app/components/pages/create-account/import-account/private-key.js
deleted file mode 100644
index 8db1bfbdd..000000000
--- a/ui/app/components/pages/create-account/import-account/private-key.js
+++ /dev/null
@@ -1,112 +0,0 @@
-const inherits = require('util').inherits
-const Component = require('react').Component
-const h = require('react-hyperscript')
-const { withRouter } = require('react-router-dom')
-const { compose } = require('recompose')
-const PropTypes = require('prop-types')
-const connect = require('react-redux').connect
-const actions = require('../../../../actions')
-const { DEFAULT_ROUTE } = require('../../../../routes')
-import Button from '../../../button'
-
-PrivateKeyImportView.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(PrivateKeyImportView)
-
-
-function mapStateToProps (state) {
- return {
- error: state.appState.warning,
- firstAddress: Object.keys(state.metamask.accounts)[0],
- }
-}
-
-function mapDispatchToProps (dispatch) {
- return {
- importNewAccount: (strategy, [ privateKey ]) => {
- return dispatch(actions.importNewAccount(strategy, [ privateKey ]))
- },
- displayWarning: (message) => dispatch(actions.displayWarning(message || null)),
- setSelectedAddress: (address) => dispatch(actions.setSelectedAddress(address)),
- }
-}
-
-inherits(PrivateKeyImportView, Component)
-function PrivateKeyImportView () {
- this.createKeyringOnEnter = this.createKeyringOnEnter.bind(this)
- Component.call(this)
-}
-
-PrivateKeyImportView.prototype.render = function () {
- const { error, displayWarning } = this.props
-
- return (
- h('div.new-account-import-form__private-key', [
-
- h('span.new-account-create-form__instruction', this.context.t('pastePrivateKey')),
-
- h('div.new-account-import-form__private-key-password-container', [
-
- h('input.new-account-import-form__input-password', {
- type: 'password',
- id: 'private-key-box',
- onKeyPress: e => this.createKeyringOnEnter(e),
- }),
-
- ]),
-
- h('div.new-account-import-form__buttons', {}, [
-
- h(Button, {
- type: 'default',
- large: true,
- className: 'new-account-create-form__button',
- onClick: () => {
- displayWarning(null)
- this.props.history.push(DEFAULT_ROUTE)
- },
- }, [this.context.t('cancel')]),
-
- h(Button, {
- type: 'primary',
- large: true,
- className: 'new-account-create-form__button',
- onClick: () => this.createNewKeychain(),
- }, [this.context.t('import')]),
-
- ]),
-
- error ? h('span.error', error) : null,
- ])
- )
-}
-
-PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) {
- if (event.key === 'Enter') {
- event.preventDefault()
- this.createNewKeychain()
- }
-}
-
-PrivateKeyImportView.prototype.createNewKeychain = function () {
- const input = document.getElementById('private-key-box')
- const privateKey = input.value
- const { importNewAccount, history, displayWarning, setSelectedAddress, firstAddress } = this.props
-
- importNewAccount('Private Key', [ privateKey ])
- .then(({ selectedAddress }) => {
- if (selectedAddress) {
- history.push(DEFAULT_ROUTE)
- displayWarning(null)
- } else {
- displayWarning('Error importing account.')
- setSelectedAddress(firstAddress)
- }
- })
- .catch(err => err && displayWarning(err.message || err))
-}
diff --git a/ui/app/components/pages/create-account/import-account/seed.js b/ui/app/components/pages/create-account/import-account/seed.js
deleted file mode 100644
index d98909baa..000000000
--- a/ui/app/components/pages/create-account/import-account/seed.js
+++ /dev/null
@@ -1,35 +0,0 @@
-const inherits = require('util').inherits
-const Component = require('react').Component
-const h = require('react-hyperscript')
-const PropTypes = require('prop-types')
-const connect = require('react-redux').connect
-
-SeedImportSubview.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect(mapStateToProps)(SeedImportSubview)
-
-
-function mapStateToProps (state) {
- return {}
-}
-
-inherits(SeedImportSubview, Component)
-function SeedImportSubview () {
- Component.call(this)
-}
-
-SeedImportSubview.prototype.render = function () {
- return (
- h('div', {
- style: {
- },
- }, [
- this.context.t('pasteSeed'),
- h('textarea'),
- h('br'),
- h('button', this.context.t('submit')),
- ])
- )
-}
diff --git a/ui/app/components/pages/create-account/index.js b/ui/app/components/pages/create-account/index.js
deleted file mode 100644
index d3de1ea01..000000000
--- a/ui/app/components/pages/create-account/index.js
+++ /dev/null
@@ -1,113 +0,0 @@
-const Component = require('react').Component
-const { Switch, Route, matchPath } = require('react-router-dom')
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const connect = require('react-redux').connect
-const actions = require('../../../actions')
-const { getCurrentViewContext } = require('../../../selectors')
-const classnames = require('classnames')
-const NewAccountCreateForm = require('./new-account')
-const NewAccountImportForm = require('./import-account')
-const ConnectHardwareForm = require('./connect-hardware')
-const {
- NEW_ACCOUNT_ROUTE,
- IMPORT_ACCOUNT_ROUTE,
- CONNECT_HARDWARE_ROUTE,
-} = require('../../../routes')
-
-class CreateAccountPage extends Component {
- renderTabs () {
- const { history, location } = this.props
-
- return h('div.new-account__tabs', [
- h('div.new-account__tabs__tab', {
- className: classnames('new-account__tabs__tab', {
- 'new-account__tabs__selected': matchPath(location.pathname, {
- path: NEW_ACCOUNT_ROUTE, exact: true,
- }),
- }),
- onClick: () => history.push(NEW_ACCOUNT_ROUTE),
- }, [
- this.context.t('create'),
- ]),
-
- h('div.new-account__tabs__tab', {
- className: classnames('new-account__tabs__tab', {
- 'new-account__tabs__selected': matchPath(location.pathname, {
- path: IMPORT_ACCOUNT_ROUTE, exact: true,
- }),
- }),
- onClick: () => history.push(IMPORT_ACCOUNT_ROUTE),
- }, [
- this.context.t('import'),
- ]),
- h(
- 'div.new-account__tabs__tab',
- {
- className: classnames('new-account__tabs__tab', {
- 'new-account__tabs__selected': matchPath(location.pathname, {
- path: CONNECT_HARDWARE_ROUTE,
- exact: true,
- }),
- }),
- onClick: () => history.push(CONNECT_HARDWARE_ROUTE),
- },
- this.context.t('connect')
- ),
- ])
- }
-
- render () {
- return h('div.new-account', {}, [
- h('div.new-account__header', [
- h('div.new-account__title', this.context.t('newAccount')),
- this.renderTabs(),
- ]),
- h('div.new-account__form', [
- h(Switch, [
- h(Route, {
- exact: true,
- path: NEW_ACCOUNT_ROUTE,
- component: NewAccountCreateForm,
- }),
- h(Route, {
- exact: true,
- path: IMPORT_ACCOUNT_ROUTE,
- component: NewAccountImportForm,
- }),
- h(Route, {
- exact: true,
- path: CONNECT_HARDWARE_ROUTE,
- component: ConnectHardwareForm,
- }),
- ]),
- ]),
- ])
- }
-}
-
-CreateAccountPage.propTypes = {
- location: PropTypes.object,
- history: PropTypes.object,
- t: PropTypes.func,
-}
-
-CreateAccountPage.contextTypes = {
- t: PropTypes.func,
-}
-
-const mapStateToProps = state => ({
- displayedForm: getCurrentViewContext(state),
-})
-
-const mapDispatchToProps = dispatch => ({
- displayForm: form => dispatch(actions.setNewAccountForm(form)),
- showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
- showExportPrivateKeyModal: () => {
- dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
- },
- hideModal: () => dispatch(actions.hideModal()),
- setAccountLabel: (address, label) => dispatch(actions.setAccountLabel(address, label)),
-})
-
-module.exports = connect(mapStateToProps, mapDispatchToProps)(CreateAccountPage)
diff --git a/ui/app/components/pages/create-account/new-account.js b/ui/app/components/pages/create-account/new-account.js
deleted file mode 100644
index 94a5fa487..000000000
--- a/ui/app/components/pages/create-account/new-account.js
+++ /dev/null
@@ -1,108 +0,0 @@
-const { Component } = require('react')
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const connect = require('react-redux').connect
-const actions = require('../../../actions')
-const { DEFAULT_ROUTE } = require('../../../routes')
-import Button from '../../button'
-
-class NewAccountCreateForm extends Component {
- constructor (props, context) {
- super(props)
-
- const { numberOfExistingAccounts = 0 } = props
- const newAccountNumber = numberOfExistingAccounts + 1
-
- this.state = {
- newAccountName: '',
- defaultAccountName: context.t('newAccountNumberName', [newAccountNumber]),
- }
- }
-
- render () {
- const { newAccountName, defaultAccountName } = this.state
- const { history, createAccount } = this.props
-
- return h('div.new-account-create-form', [
-
- h('div.new-account-create-form__input-label', {}, [
- this.context.t('accountName'),
- ]),
-
- h('div.new-account-create-form__input-wrapper', {}, [
- h('input.new-account-create-form__input', {
- value: newAccountName,
- placeholder: defaultAccountName,
- onChange: event => this.setState({ newAccountName: event.target.value }),
- }, []),
- ]),
-
- h('div.new-account-create-form__buttons', {}, [
-
- h(Button, {
- type: 'default',
- large: true,
- className: 'new-account-create-form__button',
- onClick: () => history.push(DEFAULT_ROUTE),
- }, [this.context.t('cancel')]),
-
- h(Button, {
- type: 'primary',
- large: true,
- className: 'new-account-create-form__button',
- onClick: () => {
- createAccount(newAccountName || defaultAccountName)
- .then(() => history.push(DEFAULT_ROUTE))
- },
- }, [this.context.t('create')]),
-
- ]),
-
- ])
- }
-}
-
-NewAccountCreateForm.propTypes = {
- hideModal: PropTypes.func,
- showImportPage: PropTypes.func,
- showConnectPage: PropTypes.func,
- createAccount: PropTypes.func,
- numberOfExistingAccounts: PropTypes.number,
- history: PropTypes.object,
- t: PropTypes.func,
-}
-
-const mapStateToProps = state => {
- const { metamask: { network, selectedAddress, identities = {} } } = state
- const numberOfExistingAccounts = Object.keys(identities).length
-
- return {
- network,
- address: selectedAddress,
- numberOfExistingAccounts,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- toCoinbase: address => dispatch(actions.buyEth({ network: '1', address, amount: 0 })),
- hideModal: () => dispatch(actions.hideModal()),
- createAccount: newAccountName => {
- return dispatch(actions.addNewAccount())
- .then(newAccountAddress => {
- if (newAccountName) {
- dispatch(actions.setAccountLabel(newAccountAddress, newAccountName))
- }
- })
- },
- showImportPage: () => dispatch(actions.showImportPage()),
- showConnectPage: () => dispatch(actions.showConnectPage()),
- }
-}
-
-NewAccountCreateForm.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect(mapStateToProps, mapDispatchToProps)(NewAccountCreateForm)
-
diff --git a/ui/app/components/pages/home/home.component.js b/ui/app/components/pages/home/home.component.js
deleted file mode 100644
index b9ec3c258..000000000
--- a/ui/app/components/pages/home/home.component.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import Media from 'react-media'
-import { Redirect } from 'react-router-dom'
-import WalletView from '../../wallet-view'
-import TransactionView from '../../transaction-view'
-import ProviderApproval from '../provider-approval'
-
-import {
- INITIALIZE_BACKUP_PHRASE_ROUTE,
- RESTORE_VAULT_ROUTE,
- CONFIRM_TRANSACTION_ROUTE,
- NOTICE_ROUTE,
- CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
-} from '../../../routes'
-
-export default class Home extends PureComponent {
- static propTypes = {
- history: PropTypes.object,
- noActiveNotices: PropTypes.bool,
- lostAccounts: PropTypes.array,
- forgottenPassword: PropTypes.bool,
- seedWords: PropTypes.string,
- suggestedTokens: PropTypes.object,
- unconfirmedTransactionsCount: PropTypes.number,
- providerRequests: PropTypes.array,
- }
-
- 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 () {
- const {
- noActiveNotices,
- lostAccounts,
- forgottenPassword,
- seedWords,
- providerRequests,
- } = this.props
-
- // notices
- if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) {
- return <Redirect to={{ pathname: NOTICE_ROUTE }} />
- }
-
- // seed words
- if (seedWords) {
- return <Redirect to={{ pathname: INITIALIZE_BACKUP_PHRASE_ROUTE }}/>
- }
-
- if (forgottenPassword) {
- return <Redirect to={{ pathname: RESTORE_VAULT_ROUTE }} />
- }
-
- if (providerRequests && providerRequests.length > 0) {
- return (
- <ProviderApproval providerRequest={providerRequests[0]} />
- )
- }
-
- return (
- <div className="main-container">
- <div className="account-and-transaction-details">
- <Media
- query="(min-width: 576px)"
- render={() => <WalletView />}
- />
- <TransactionView />
- </div>
- </div>
- )
- }
-}
diff --git a/ui/app/components/pages/home/home.container.js b/ui/app/components/pages/home/home.container.js
deleted file mode 100644
index bb8cf5e81..000000000
--- a/ui/app/components/pages/home/home.container.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import Home from './home.component'
-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 {
- noActiveNotices,
- lostAccounts,
- seedWords,
- suggestedTokens,
- providerRequests,
- } = metamask
- const { forgottenPassword } = appState
-
- return {
- noActiveNotices,
- lostAccounts,
- forgottenPassword,
- seedWords,
- suggestedTokens,
- unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state),
- providerRequests,
- }
-}
-
-export default compose(
- withRouter,
- connect(mapStateToProps)
-)(Home)
diff --git a/ui/app/components/pages/home/index.js b/ui/app/components/pages/home/index.js
deleted file mode 100644
index 4474ba5b8..000000000
--- a/ui/app/components/pages/home/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './home.container'
diff --git a/ui/app/components/pages/index.scss b/ui/app/components/pages/index.scss
deleted file mode 100644
index 6551278f5..000000000
--- a/ui/app/components/pages/index.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-@import './unlock-page/index';
-
-@import './add-token/index';
-
-@import './confirm-add-token/index';
-
-@import './settings/index';
diff --git a/ui/app/components/pages/initialized.js b/ui/app/components/pages/initialized.js
deleted file mode 100644
index 3adf67b28..000000000
--- a/ui/app/components/pages/initialized.js
+++ /dev/null
@@ -1,25 +0,0 @@
-const { connect } = require('react-redux')
-const PropTypes = require('prop-types')
-const { Redirect } = require('react-router-dom')
-const h = require('react-hyperscript')
-const { INITIALIZE_ROUTE } = require('../../routes')
-const MetamaskRoute = require('./metamask-route')
-
-const Initialized = props => {
- return props.isInitialized
- ? h(MetamaskRoute, { ...props })
- : h(Redirect, { to: { pathname: INITIALIZE_ROUTE } })
-}
-
-Initialized.propTypes = {
- isInitialized: PropTypes.bool,
-}
-
-const mapStateToProps = state => {
- const { metamask: { isInitialized } } = state
- return {
- isInitialized,
- }
-}
-
-module.exports = connect(mapStateToProps)(Initialized)
diff --git a/ui/app/components/pages/keychains/restore-vault.js b/ui/app/components/pages/keychains/restore-vault.js
deleted file mode 100644
index d90a33e49..000000000
--- a/ui/app/components/pages/keychains/restore-vault.js
+++ /dev/null
@@ -1,189 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import {connect} from 'react-redux'
-import {
- createNewVaultAndRestore,
- unMarkPasswordForgotten,
-} from '../../../actions'
-import { DEFAULT_ROUTE } from '../../../routes'
-import TextField from '../../text-field'
-
-class RestoreVaultPage extends Component {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- warning: PropTypes.string,
- createNewVaultAndRestore: PropTypes.func.isRequired,
- leaveImportSeedScreenState: PropTypes.func,
- history: PropTypes.object,
- isLoading: PropTypes.bool,
- };
-
- state = {
- seedPhrase: '',
- password: '',
- confirmPassword: '',
- seedPhraseError: null,
- passwordError: null,
- confirmPasswordError: null,
- }
-
- parseSeedPhrase = (seedPhrase) => {
- return seedPhrase
- .match(/\w+/g)
- .join(' ')
- }
-
- handleSeedPhraseChange (seedPhrase) {
- let seedPhraseError = null
-
- if (seedPhrase && this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) {
- seedPhraseError = this.context.t('seedPhraseReq')
- }
-
- this.setState({ seedPhrase, seedPhraseError })
- }
-
- handlePasswordChange (password) {
- const { confirmPassword } = this.state
- let confirmPasswordError = null
- let passwordError = null
-
- if (password && password.length < 8) {
- passwordError = this.context.t('passwordNotLongEnough')
- }
-
- if (confirmPassword && password !== confirmPassword) {
- confirmPasswordError = this.context.t('passwordsDontMatch')
- }
-
- this.setState({ password, passwordError, confirmPasswordError })
- }
-
- handleConfirmPasswordChange (confirmPassword) {
- const { password } = this.state
- let confirmPasswordError = null
-
- if (password !== confirmPassword) {
- confirmPasswordError = this.context.t('passwordsDontMatch')
- }
-
- this.setState({ confirmPassword, confirmPasswordError })
- }
-
- onClick = () => {
- const { password, seedPhrase } = this.state
- const {
- createNewVaultAndRestore,
- leaveImportSeedScreenState,
- history,
- } = this.props
-
- leaveImportSeedScreenState()
- createNewVaultAndRestore(password, this.parseSeedPhrase(seedPhrase))
- .then(() => history.push(DEFAULT_ROUTE))
- }
-
- hasError () {
- const { passwordError, confirmPasswordError, seedPhraseError } = this.state
- return passwordError || confirmPasswordError || seedPhraseError
- }
-
- render () {
- const {
- seedPhrase,
- password,
- confirmPassword,
- seedPhraseError,
- passwordError,
- confirmPasswordError,
- } = this.state
- const { t } = this.context
- const { isLoading } = this.props
- const disabled = !seedPhrase || !password || !confirmPassword || isLoading || this.hasError()
-
- return (
- <div className="first-view-main-wrapper">
- <div className="first-view-main">
- <div className="import-account">
- <a
- className="import-account__back-button"
- onClick={e => {
- e.preventDefault()
- this.props.history.goBack()
- }}
- href="#"
- >
- {`< Back`}
- </a>
- <div className="import-account__title">
- { this.context.t('restoreAccountWithSeed') }
- </div>
- <div className="import-account__selector-label">
- { this.context.t('secretPhrase') }
- </div>
- <div className="import-account__input-wrapper">
- <label className="import-account__input-label">Wallet Seed</label>
- <textarea
- className="import-account__secret-phrase"
- onChange={e => this.handleSeedPhraseChange(e.target.value)}
- value={this.state.seedPhrase}
- placeholder={this.context.t('separateEachWord')}
- />
- </div>
- <span className="error">
- { seedPhraseError }
- </span>
- <TextField
- id="password"
- label={t('newPassword')}
- type="password"
- className="first-time-flow__input"
- value={this.state.password}
- onChange={event => this.handlePasswordChange(event.target.value)}
- error={passwordError}
- autoComplete="new-password"
- margin="normal"
- largeLabel
- />
- <TextField
- id="confirm-password"
- label={t('confirmPassword')}
- type="password"
- className="first-time-flow__input"
- value={this.state.confirmPassword}
- onChange={event => this.handleConfirmPasswordChange(event.target.value)}
- error={confirmPasswordError}
- autoComplete="confirm-password"
- margin="normal"
- largeLabel
- />
- <button
- className="first-time-flow__button"
- onClick={() => !disabled && this.onClick()}
- disabled={disabled}
- >
- {this.context.t('restore')}
- </button>
- </div>
- </div>
- </div>
- )
- }
-}
-
-RestoreVaultPage.contextTypes = {
- t: PropTypes.func,
-}
-
-export default connect(
- ({ appState: { warning, isLoading } }) => ({ warning, isLoading }),
- dispatch => ({
- leaveImportSeedScreenState: () => {
- dispatch(unMarkPasswordForgotten())
- },
- createNewVaultAndRestore: (pw, seed) => dispatch(createNewVaultAndRestore(pw, seed)),
- })
-)(RestoreVaultPage)
diff --git a/ui/app/components/pages/keychains/reveal-seed.js b/ui/app/components/pages/keychains/reveal-seed.js
deleted file mode 100644
index 32557066f..000000000
--- a/ui/app/components/pages/keychains/reveal-seed.js
+++ /dev/null
@@ -1,177 +0,0 @@
-const { Component } = require('react')
-const { connect } = require('react-redux')
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const classnames = require('classnames')
-
-const { requestRevealSeedWords } = require('../../../actions')
-const { DEFAULT_ROUTE } = require('../../../routes')
-const ExportTextContainer = require('../../export-text-container')
-
-import Button from '../../button'
-
-const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN'
-const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN'
-
-class RevealSeedPage extends Component {
- constructor (props) {
- super(props)
-
- this.state = {
- screen: PASSWORD_PROMPT_SCREEN,
- password: '',
- seedWords: null,
- error: null,
- }
- }
-
- componentDidMount () {
- const passwordBox = document.getElementById('password-box')
- if (passwordBox) {
- passwordBox.focus()
- }
- }
-
- handleSubmit (event) {
- event.preventDefault()
- this.setState({ seedWords: null, error: null })
- this.props.requestRevealSeedWords(this.state.password)
- .then(seedWords => this.setState({ seedWords, screen: REVEAL_SEED_SCREEN }))
- .catch(error => this.setState({ error: error.message }))
- }
-
- renderWarning () {
- return (
- h('.page-container__warning-container', [
- h('img.page-container__warning-icon', {
- src: 'images/warning.svg',
- }),
- h('.page-container__warning-message', [
- h('.page-container__warning-title', [this.context.t('revealSeedWordsWarningTitle')]),
- h('div', [this.context.t('revealSeedWordsWarning')]),
- ]),
- ])
- )
- }
-
- renderContent () {
- return this.state.screen === PASSWORD_PROMPT_SCREEN
- ? this.renderPasswordPromptContent()
- : this.renderRevealSeedContent()
- }
-
- renderPasswordPromptContent () {
- const { t } = this.context
-
- return (
- h('form', {
- onSubmit: event => this.handleSubmit(event),
- }, [
- h('label.input-label', {
- htmlFor: 'password-box',
- }, t('enterPasswordContinue')),
- h('.input-group', [
- h('input.form-control', {
- type: 'password',
- placeholder: t('password'),
- id: 'password-box',
- value: this.state.password,
- onChange: event => this.setState({ password: event.target.value }),
- className: classnames({ 'form-control--error': this.state.error }),
- }),
- ]),
- this.state.error && h('.reveal-seed__error', this.state.error),
- ])
- )
- }
-
- renderRevealSeedContent () {
- const { t } = this.context
-
- return (
- h('div', [
- h('label.reveal-seed__label', t('yourPrivateSeedPhrase')),
- h(ExportTextContainer, {
- text: this.state.seedWords,
- filename: t('metamaskSeedWords'),
- }),
- ])
- )
- }
-
- renderFooter () {
- return this.state.screen === PASSWORD_PROMPT_SCREEN
- ? this.renderPasswordPromptFooter()
- : this.renderRevealSeedFooter()
- }
-
- renderPasswordPromptFooter () {
- return (
- h('.page-container__footer', [
- h('header', [
- h(Button, {
- type: 'default',
- large: true,
- className: 'page-container__footer-button',
- onClick: () => this.props.history.push(DEFAULT_ROUTE),
- }, this.context.t('cancel')),
- h(Button, {
- type: 'primary',
- large: true,
- className: 'page-container__footer-button',
- onClick: event => this.handleSubmit(event),
- disabled: this.state.password === '',
- }, this.context.t('next')),
- ]),
- ])
- )
- }
-
- renderRevealSeedFooter () {
- return (
- h('.page-container__footer', [
- h(Button, {
- type: 'default',
- large: true,
- className: 'page-container__footer-button',
- onClick: () => this.props.history.push(DEFAULT_ROUTE),
- }, this.context.t('close')),
- ])
- )
- }
-
- render () {
- return (
- h('.page-container', [
- h('.page-container__header', [
- h('.page-container__title', this.context.t('revealSeedWordsTitle')),
- h('.page-container__subtitle', this.context.t('revealSeedWordsDescription')),
- ]),
- h('.page-container__content', [
- this.renderWarning(),
- h('.reveal-seed__content', [
- this.renderContent(),
- ]),
- ]),
- this.renderFooter(),
- ])
- )
- }
-}
-
-RevealSeedPage.propTypes = {
- requestRevealSeedWords: PropTypes.func,
- history: PropTypes.object,
-}
-
-RevealSeedPage.contextTypes = {
- t: PropTypes.func,
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- requestRevealSeedWords: password => dispatch(requestRevealSeedWords(password)),
- }
-}
-
-module.exports = connect(null, mapDispatchToProps)(RevealSeedPage)
diff --git a/ui/app/components/pages/metamask-route.js b/ui/app/components/pages/metamask-route.js
deleted file mode 100644
index 23c5b5199..000000000
--- a/ui/app/components/pages/metamask-route.js
+++ /dev/null
@@ -1,28 +0,0 @@
-const { connect } = require('react-redux')
-const PropTypes = require('prop-types')
-const { Route } = require('react-router-dom')
-const h = require('react-hyperscript')
-
-const MetamaskRoute = ({ component, mascaraComponent, isMascara, ...props }) => {
- return (
- h(Route, {
- ...props,
- component: isMascara && mascaraComponent ? mascaraComponent : component,
- })
- )
-}
-
-MetamaskRoute.propTypes = {
- component: PropTypes.func,
- mascaraComponent: PropTypes.func,
- isMascara: PropTypes.bool,
-}
-
-const mapStateToProps = state => {
- const { metamask: { isMascara } } = state
- return {
- isMascara,
- }
-}
-
-module.exports = connect(mapStateToProps)(MetamaskRoute)
diff --git a/ui/app/components/pages/notice.js b/ui/app/components/pages/notice.js
deleted file mode 100644
index a9077b98b..000000000
--- a/ui/app/components/pages/notice.js
+++ /dev/null
@@ -1,203 +0,0 @@
-const { Component } = require('react')
-const h = require('react-hyperscript')
-const { connect } = require('react-redux')
-const PropTypes = require('prop-types')
-const ReactMarkdown = require('react-markdown')
-const linker = require('extension-link-enabler')
-const generateLostAccountsNotice = require('../../../lib/lost-accounts-notice')
-const findDOMNode = require('react-dom').findDOMNode
-const actions = require('../../actions')
-const { DEFAULT_ROUTE } = require('../../routes')
-
-class Notice extends Component {
- constructor (props) {
- super(props)
-
- this.state = {
- disclaimerDisabled: true,
- }
- }
-
- componentWillMount () {
- if (!this.props.notice) {
- this.props.history.push(DEFAULT_ROUTE)
- }
- }
-
- componentDidMount () {
- // eslint-disable-next-line react/no-find-dom-node
- var node = findDOMNode(this)
- linker.setupListener(node)
- if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
- this.setState({ disclaimerDisabled: false })
- }
- }
-
- componentWillReceiveProps (nextProps) {
- if (!nextProps.notice) {
- this.props.history.push(DEFAULT_ROUTE)
- }
- }
-
- componentWillUnmount () {
- // eslint-disable-next-line react/no-find-dom-node
- var node = findDOMNode(this)
- linker.teardownListener(node)
- }
-
- handleAccept () {
- this.setState({ disclaimerDisabled: true })
- this.props.onConfirm()
- }
-
- render () {
- const { notice = {} } = this.props
- const { title, date, body } = notice
- const { disclaimerDisabled } = this.state
-
- return (
- h('.flex-column.flex-center.flex-grow', {
- style: {
- width: '100%',
- },
- }, [
- h('h3.flex-center.text-transform-uppercase.terms-header', {
- style: {
- background: '#EBEBEB',
- color: '#AEAEAE',
- width: '100%',
- fontSize: '20px',
- textAlign: 'center',
- padding: 6,
- },
- }, [
- title,
- ]),
-
- h('h5.flex-center.text-transform-uppercase.terms-header', {
- style: {
- background: '#EBEBEB',
- color: '#AEAEAE',
- marginBottom: 24,
- width: '100%',
- fontSize: '20px',
- textAlign: 'center',
- padding: 6,
- },
- }, [
- date,
- ]),
-
- h('style', `
-
- .markdown {
- overflow-x: hidden;
- }
-
- .markdown h1, .markdown h2, .markdown h3 {
- margin: 10px 0;
- font-weight: bold;
- }
-
- .markdown strong {
- font-weight: bold;
- }
- .markdown em {
- font-style: italic;
- }
-
- .markdown p {
- margin: 10px 0;
- }
-
- .markdown a {
- color: #df6b0e;
- }
-
- `),
-
- h('div.markdown', {
- onScroll: (e) => {
- var object = e.currentTarget
- if (object.offsetHeight + object.scrollTop + 100 >= object.scrollHeight) {
- this.setState({ disclaimerDisabled: false })
- }
- },
- style: {
- background: 'rgb(235, 235, 235)',
- height: '310px',
- padding: '6px',
- width: '90%',
- overflowY: 'scroll',
- scroll: 'auto',
- },
- }, [
- h(ReactMarkdown, {
- className: 'notice-box',
- source: body,
- skipHtml: true,
- }),
- ]),
-
- h('button.primary', {
- disabled: disclaimerDisabled,
- onClick: () => this.handleAccept(),
- style: {
- marginTop: '18px',
- },
- }, 'Accept'),
- ])
- )
- }
-
-}
-
-const mapStateToProps = state => {
- const { metamask } = state
- const { noActiveNotices, nextUnreadNotice, lostAccounts } = metamask
-
- return {
- noActiveNotices,
- nextUnreadNotice,
- lostAccounts,
- }
-}
-
-Notice.propTypes = {
- notice: PropTypes.object,
- onConfirm: PropTypes.func,
- history: PropTypes.object,
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- markNoticeRead: nextUnreadNotice => dispatch(actions.markNoticeRead(nextUnreadNotice)),
- markAccountsFound: () => dispatch(actions.markAccountsFound()),
- }
-}
-
-const mergeProps = (stateProps, dispatchProps, ownProps) => {
- const { noActiveNotices, nextUnreadNotice, lostAccounts } = stateProps
- const { markNoticeRead, markAccountsFound } = dispatchProps
-
- let notice
- let onConfirm
-
- if (!noActiveNotices) {
- notice = nextUnreadNotice
- onConfirm = () => markNoticeRead(nextUnreadNotice)
- } else if (lostAccounts && lostAccounts.length > 0) {
- notice = generateLostAccountsNotice(lostAccounts)
- onConfirm = () => markAccountsFound()
- }
-
- return {
- ...stateProps,
- ...dispatchProps,
- ...ownProps,
- notice,
- onConfirm,
- }
-}
-
-module.exports = connect(mapStateToProps, mapDispatchToProps, mergeProps)(Notice)
diff --git a/ui/app/components/pages/provider-approval/index.js b/ui/app/components/pages/provider-approval/index.js
deleted file mode 100644
index 4162f3155..000000000
--- a/ui/app/components/pages/provider-approval/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './provider-approval.container'
diff --git a/ui/app/components/pages/provider-approval/provider-approval.component.js b/ui/app/components/pages/provider-approval/provider-approval.component.js
deleted file mode 100644
index da98bc3fc..000000000
--- a/ui/app/components/pages/provider-approval/provider-approval.component.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import PropTypes from 'prop-types'
-import React, { Component } from 'react'
-import ProviderPageContainer from '../../provider-page-container'
-
-export default class ProviderApproval extends Component {
- static propTypes = {
- approveProviderRequest: PropTypes.func.isRequired,
- providerRequest: PropTypes.object.isRequired,
- rejectProviderRequest: PropTypes.func.isRequired,
- };
-
- static contextTypes = {
- t: PropTypes.func,
- };
-
- render () {
- const { approveProviderRequest, providerRequest, rejectProviderRequest } = this.props
- return (
- <ProviderPageContainer
- approveProviderRequest={approveProviderRequest}
- origin={providerRequest.origin}
- rejectProviderRequest={rejectProviderRequest}
- siteImage={providerRequest.siteImage}
- siteTitle={providerRequest.siteTitle}
- />
- )
- }
-}
diff --git a/ui/app/components/pages/provider-approval/provider-approval.container.js b/ui/app/components/pages/provider-approval/provider-approval.container.js
deleted file mode 100644
index b223244a1..000000000
--- a/ui/app/components/pages/provider-approval/provider-approval.container.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { connect } from 'react-redux'
-import ProviderApproval from './provider-approval.component'
-import { approveProviderRequest, rejectProviderRequest } from '../../../actions'
-
-function mapDispatchToProps (dispatch) {
- return {
- approveProviderRequest: origin => dispatch(approveProviderRequest(origin)),
- rejectProviderRequest: origin => dispatch(rejectProviderRequest(origin)),
- }
-}
-
-export default connect(null, mapDispatchToProps)(ProviderApproval)
diff --git a/ui/app/components/pages/settings/index.js b/ui/app/components/pages/settings/index.js
deleted file mode 100644
index 44a9ffa63..000000000
--- a/ui/app/components/pages/settings/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './settings.component'
diff --git a/ui/app/components/pages/settings/index.scss b/ui/app/components/pages/settings/index.scss
deleted file mode 100644
index 138ebcfc5..000000000
--- a/ui/app/components/pages/settings/index.scss
+++ /dev/null
@@ -1,80 +0,0 @@
-@import './info-tab/index';
-
-@import './settings-tab/index';
-
-.settings-page {
- position: relative;
- background: $white;
- display: flex;
- flex-flow: column nowrap;
-
- &__header {
- padding: 25px 25px 0;
- }
-
- &__close-button::after {
- content: '\00D7';
- font-size: 40px;
- color: $dusty-gray;
- position: absolute;
- top: 25px;
- right: 30px;
- cursor: pointer;
- }
-
- &__content {
- padding: 25px;
- height: auto;
- overflow: auto;
- }
-
- &__content-row {
- display: flex;
- flex-direction: row;
- padding: 10px 0 20px;
-
- @media screen and (max-width: 575px) {
- flex-direction: column;
- padding: 10px 0;
- }
- }
-
- &__content-item {
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
- padding: 0 5px;
- min-height: 71px;
-
- @media screen and (max-width: 575px) {
- height: initial;
- padding: 5px 0;
- }
-
- &--without-height {
- height: initial;
- }
- }
-
- &__content-label {
- text-transform: capitalize;
- }
-
- &__content-description {
- font-size: 14px;
- color: $dusty-gray;
- padding-top: 5px;
- }
-
- &__content-item-col {
- max-width: 300px;
- display: flex;
- flex-direction: column;
-
- @media screen and (max-width: 575px) {
- max-width: 100%;
- width: 100%;
- }
- }
-}
diff --git a/ui/app/components/pages/settings/info-tab/index.js b/ui/app/components/pages/settings/info-tab/index.js
deleted file mode 100644
index 7556a258d..000000000
--- a/ui/app/components/pages/settings/info-tab/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './info-tab.component'
diff --git a/ui/app/components/pages/settings/info-tab/index.scss b/ui/app/components/pages/settings/info-tab/index.scss
deleted file mode 100644
index 43ad6f652..000000000
--- a/ui/app/components/pages/settings/info-tab/index.scss
+++ /dev/null
@@ -1,56 +0,0 @@
-.info-tab {
- &__logo-wrapper {
- height: 80px;
- margin-bottom: 20px;
- }
-
- &__logo {
- max-height: 100%;
- max-width: 100%;
- }
-
- &__item {
- padding: 10px 0;
- }
-
- &__link-header {
- padding-bottom: 15px;
-
- @media screen and (max-width: 575px) {
- padding-bottom: 5px;
- }
- }
-
- &__link-item {
- padding: 15px 0;
-
- @media screen and (max-width: 575px) {
- padding: 5px 0;
- }
- }
-
- &__link-text {
- color: $curious-blue;
- }
-
- &__version-number {
- padding-top: 5px;
- font-size: 13px;
- color: $dusty-gray;
- }
-
- &__separator {
- margin: 15px 0;
- width: 80px;
- border-color: $alto;
- border: none;
- height: 1px;
- background-color: $alto;
- color: $alto;
- }
-
- &__about {
- color: $dusty-gray;
- margin-bottom: 15px;
- }
-}
diff --git a/ui/app/components/pages/settings/info-tab/info-tab.component.js b/ui/app/components/pages/settings/info-tab/info-tab.component.js
deleted file mode 100644
index 72f7d835e..000000000
--- a/ui/app/components/pages/settings/info-tab/info-tab.component.js
+++ /dev/null
@@ -1,136 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-
-export default class InfoTab extends PureComponent {
- state = {
- version: global.platform.getVersion(),
- }
-
- static propTypes = {
- tab: PropTypes.string,
- metamask: PropTypes.object,
- setCurrentCurrency: PropTypes.func,
- setRpcTarget: PropTypes.func,
- displayWarning: PropTypes.func,
- revealSeedConfirmation: PropTypes.func,
- warning: PropTypes.string,
- location: PropTypes.object,
- history: PropTypes.object,
- }
-
- static contextTypes = {
- t: PropTypes.func,
- }
-
- renderInfoLinks () {
- const { t } = this.context
-
- return (
- <div className="settings-page__content-item settings-page__content-item--without-height">
- <div className="info-tab__link-header">
- { t('links') }
- </div>
- <div className="info-tab__link-item">
- <a
- href="https://metamask.io/privacy.html"
- target="_blank"
- rel="noopener noreferrer"
- >
- <span className="info-tab__link-text">
- { t('privacyMsg') }
- </span>
- </a>
- </div>
- <div className="info-tab__link-item">
- <a
- href="https://metamask.io/terms.html"
- target="_blank"
- rel="noopener noreferrer"
- >
- <span className="info-tab__link-text">
- { t('terms') }
- </span>
- </a>
- </div>
- <div className="info-tab__link-item">
- <a
- href="https://metamask.io/attributions.html"
- target="_blank"
- rel="noopener noreferrer"
- >
- <span className="info-tab__link-text">
- { t('attributions') }
- </span>
- </a>
- </div>
- <hr className="info-tab__separator" />
- <div className="info-tab__link-item">
- <a
- href="https://support.metamask.io"
- target="_blank"
- rel="noopener noreferrer"
- >
- <span className="info-tab__link-text">
- { t('supportCenter') }
- </span>
- </a>
- </div>
- <div className="info-tab__link-item">
- <a
- href="https://metamask.io/"
- target="_blank"
- rel="noopener noreferrer"
- >
- <span className="info-tab__link-text">
- { t('visitWebSite') }
- </span>
- </a>
- </div>
- <div className="info-tab__link-item">
- <a
- href="mailto:help@metamask.io?subject=Feedback"
- target="_blank"
- rel="noopener noreferrer"
- >
- <span className="info-tab__link-text">
- { t('emailUs') }
- </span>
- </a>
- </div>
- </div>
- )
- }
-
- render () {
- const { t } = this.context
-
- return (
- <div className="settings-page__content">
- <div className="settings-page__content-row">
- <div className="settings-page__content-item settings-page__content-item--without-height">
- <div className="info-tab__logo-wrapper">
- <img
- src="images/info-logo.png"
- className="info-tab__logo"
- />
- </div>
- <div className="info-tab__item">
- <div className="info-tab__version-header">
- { t('metamaskVersion') }
- </div>
- <div className="info-tab__version-number">
- { this.state.version }
- </div>
- </div>
- <div className="info-tab__item">
- <div className="info-tab__about">
- { t('builtInCalifornia') }
- </div>
- </div>
- </div>
- { this.renderInfoLinks() }
- </div>
- </div>
- )
- }
-}
diff --git a/ui/app/components/pages/settings/settings-tab/index.js b/ui/app/components/pages/settings/settings-tab/index.js
deleted file mode 100644
index 9fdaafd3f..000000000
--- a/ui/app/components/pages/settings/settings-tab/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './settings-tab.container'
diff --git a/ui/app/components/pages/settings/settings-tab/index.scss b/ui/app/components/pages/settings/settings-tab/index.scss
deleted file mode 100644
index ef32b0e4c..000000000
--- a/ui/app/components/pages/settings/settings-tab/index.scss
+++ /dev/null
@@ -1,69 +0,0 @@
-.settings-tab {
- &__error {
- padding-bottom: 20px;
- text-align: center;
- color: $crimson;
- }
-
- &__advanced-link {
- color: $curious-blue;
- 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 {
- border-color: lighten($monzo, 10%);
- color: $monzo;
-
- &:active {
- background: lighten($monzo, 55%);
- border-color: $monzo;
- }
-
- &:hover {
- border-color: $monzo;
- }
- }
-
- &__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;
- }
-
- &__radio-button {
- display: flex;
- align-items: center;
-
- &:not(:last-child) {
- margin-right: 16px;
- }
- }
-
- &__radio-label {
- padding-left: 4px;
- }
-}
diff --git a/ui/app/components/pages/settings/settings-tab/settings-tab.component.js b/ui/app/components/pages/settings/settings-tab/settings-tab.component.js
deleted file mode 100644
index a0a8ed47e..000000000
--- a/ui/app/components/pages/settings/settings-tab/settings-tab.component.js
+++ /dev/null
@@ -1,544 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import infuraCurrencies from '../../../../infura-conversion.json'
-import validUrl from 'valid-url'
-import { exportAsFile } from '../../../../util'
-import SimpleDropdown from '../../../dropdowns/simple-dropdown'
-import ToggleButton from 'react-toggle-button'
-import { REVEAL_SEED_ROUTE } from '../../../../routes'
-import locales from '../../../../../../app/_locales/index.json'
-import TextField from '../../../text-field'
-import Button from '../../../button'
-
-const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
- return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase())
-})
-
-const infuraCurrencyOptions = sortedCurrencies.map(({ quote: { code, name } }) => {
- return {
- displayValue: `${code.toUpperCase()} - ${name}`,
- key: code,
- value: code,
- }
-})
-
-const localeOptions = locales.map(locale => {
- return {
- displayValue: `${locale.name}`,
- key: locale.code,
- value: locale.code,
- }
-})
-
-export default class SettingsTab extends PureComponent {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- metamask: PropTypes.object,
- setUseBlockie: PropTypes.func,
- setHexDataFeatureFlag: PropTypes.func,
- setPrivacyMode: PropTypes.func,
- privacyMode: PropTypes.bool,
- setCurrentCurrency: PropTypes.func,
- setRpcTarget: PropTypes.func,
- delRpcTarget: PropTypes.func,
- displayWarning: PropTypes.func,
- revealSeedConfirmation: PropTypes.func,
- setFeatureFlagToBeta: PropTypes.func,
- showClearApprovalModal: PropTypes.func,
- showResetAccountConfirmationModal: PropTypes.func,
- warning: PropTypes.string,
- history: PropTypes.object,
- isMascara: PropTypes.bool,
- updateCurrentLocale: PropTypes.func,
- currentLocale: PropTypes.string,
- useBlockie: PropTypes.bool,
- sendHexData: PropTypes.bool,
- currentCurrency: PropTypes.string,
- conversionDate: PropTypes.number,
- nativeCurrency: PropTypes.string,
- useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
- setUseNativeCurrencyAsPrimaryCurrencyPreference: PropTypes.func,
- }
-
- state = {
- newRpc: '',
- chainId: '',
- showOptions: false,
- ticker: '',
- nickname: '',
- }
-
- renderCurrentConversion () {
- const { t } = this.context
- const { currentCurrency, conversionDate, setCurrentCurrency } = this.props
-
- return (
- <div className="settings-page__content-row">
- <div className="settings-page__content-item">
- <span>{ t('currentConversion') }</span>
- <span className="settings-page__content-description">
- { t('updatedWithDate', [Date(conversionDate)]) }
- </span>
- </div>
- <div className="settings-page__content-item">
- <div className="settings-page__content-item-col">
- <SimpleDropdown
- placeholder={t('selectCurrency')}
- options={infuraCurrencyOptions}
- selectedOption={currentCurrency}
- onSelect={newCurrency => setCurrentCurrency(newCurrency)}
- />
- </div>
- </div>
- </div>
- )
- }
-
- renderCurrentLocale () {
- const { t } = this.context
- const { updateCurrentLocale, currentLocale } = this.props
- const currentLocaleMeta = locales.find(locale => locale.code === currentLocale)
- const currentLocaleName = currentLocaleMeta ? currentLocaleMeta.name : ''
-
- return (
- <div className="settings-page__content-row">
- <div className="settings-page__content-item">
- <span className="settings-page__content-label">
- { t('currentLanguage') }
- </span>
- <span className="settings-page__content-description">
- { currentLocaleName }
- </span>
- </div>
- <div className="settings-page__content-item">
- <div className="settings-page__content-item-col">
- <SimpleDropdown
- placeholder={t('selectLocale')}
- options={localeOptions}
- selectedOption={currentLocale}
- onSelect={async newLocale => updateCurrentLocale(newLocale)}
- />
- </div>
- </div>
- </div>
- )
- }
-
- renderNewRpcUrl () {
- const { t } = this.context
- const { newRpc, chainId, ticker, nickname } = this.state
-
- return (
- <div className="settings-page__content-row">
- <div className="settings-page__content-item">
- <span>{ t('newNetwork') }</span>
- </div>
- <div className="settings-page__content-item">
- <div className="settings-page__content-item-col">
- <TextField
- type="text"
- id="new-rpc"
- placeholder={t('rpcURL')}
- value={newRpc}
- onChange={e => this.setState({ newRpc: e.target.value })}
- onKeyPress={e => {
- if (e.key === 'Enter') {
- this.validateRpc(newRpc, chainId, ticker, nickname)
- }
- }}
- fullWidth
- margin="dense"
- />
- <TextField
- type="text"
- id="chainid"
- placeholder={t('optionalChainId')}
- value={chainId}
- onChange={e => this.setState({ chainId: e.target.value })}
- onKeyPress={e => {
- if (e.key === 'Enter') {
- this.validateRpc(newRpc, chainId, ticker, nickname)
- }
- }}
- style={{
- display: this.state.showOptions ? null : 'none',
- }}
- fullWidth
- margin="dense"
- />
- <TextField
- type="text"
- id="ticker"
- placeholder={t('optionalSymbol')}
- value={ticker}
- onChange={e => this.setState({ ticker: e.target.value })}
- onKeyPress={e => {
- if (e.key === 'Enter') {
- this.validateRpc(newRpc, chainId, ticker, nickname)
- }
- }}
- style={{
- display: this.state.showOptions ? null : 'none',
- }}
- fullWidth
- margin="dense"
- />
- <TextField
- type="text"
- id="nickname"
- placeholder={t('optionalNickname')}
- value={nickname}
- onChange={e => this.setState({ nickname: e.target.value })}
- onKeyPress={e => {
- if (e.key === 'Enter') {
- this.validateRpc(newRpc, chainId, ticker, nickname)
- }
- }}
- style={{
- display: this.state.showOptions ? null : 'none',
- }}
- fullWidth
- margin="dense"
- />
- <div className="flex-row flex-align-center space-between">
- <span className="settings-tab__advanced-link"
- onClick={e => {
- e.preventDefault()
- this.setState({ showOptions: !this.state.showOptions })
- }}
- >
- { t(this.state.showOptions ? 'hideAdvancedOptions' : 'showAdvancedOptions') }
- </span>
- <button
- className="button btn-primary settings-tab__rpc-save-button"
- onClick={e => {
- e.preventDefault()
- this.validateRpc(newRpc, chainId, ticker, nickname)
- }}
- >
- { t('save') }
- </button>
- </div>
- </div>
- </div>
- </div>
- )
- }
-
- validateRpc (newRpc, chainId, ticker = 'ETH', nickname) {
- const { setRpcTarget, displayWarning } = this.props
-
- if (validUrl.isWebUri(newRpc)) {
- setRpcTarget(newRpc, chainId, ticker, nickname)
- } else {
- const appendedRpc = `http://${newRpc}`
-
- if (validUrl.isWebUri(appendedRpc)) {
- displayWarning(this.context.t('uriErrorMsg'))
- } else {
- displayWarning(this.context.t('invalidRPC'))
- }
- }
- }
-
- renderStateLogs () {
- const { t } = this.context
- const { displayWarning } = this.props
-
- return (
- <div className="settings-page__content-row">
- <div className="settings-page__content-item">
- <span>{ t('stateLogs') }</span>
- <span className="settings-page__content-description">
- { t('stateLogsDescription') }
- </span>
- </div>
- <div className="settings-page__content-item">
- <div className="settings-page__content-item-col">
- <Button
- type="primary"
- large
- onClick={() => {
- window.logStateString((err, result) => {
- if (err) {
- displayWarning(t('stateLogError'))
- } else {
- exportAsFile('MetaMask State Logs.json', result)
- }
- })
- }}
- >
- { t('downloadStateLogs') }
- </Button>
- </div>
- </div>
- </div>
- )
- }
-
- renderClearApproval () {
- const { t } = this.context
- const { showClearApprovalModal } = this.props
- return (
- <div className="settings-page__content-row">
- <div className="settings-page__content-item">
- <span>{ t('approvalData') }</span>
- <span className="settings-page__content-description">
- { t('approvalDataDescription') }
- </span>
- </div>
- <div className="settings-page__content-item">
- <div className="settings-page__content-item-col">
- <Button
- type="secondary"
- large
- className="settings-tab__button--orange"
- onClick={event => {
- event.preventDefault()
- showClearApprovalModal()
- }}
- >
- { t('clearApprovalData') }
- </Button>
- </div>
- </div>
- </div>
- )
- }
-
- renderSeedWords () {
- const { t } = this.context
- const { history } = this.props
-
- return (
- <div className="settings-page__content-row">
- <div className="settings-page__content-item">
- <span>{ t('revealSeedWords') }</span>
- </div>
- <div className="settings-page__content-item">
- <div className="settings-page__content-item-col">
- <Button
- type="secondary"
- large
- onClick={event => {
- event.preventDefault()
- history.push(REVEAL_SEED_ROUTE)
- }}
- >
- { t('revealSeedWords') }
- </Button>
- </div>
- </div>
- </div>
- )
- }
-
- renderOldUI () {
- const { t } = this.context
- const { setFeatureFlagToBeta } = this.props
-
- return (
- <div className="settings-page__content-row">
- <div className="settings-page__content-item">
- <span>{ t('useOldUI') }</span>
- </div>
- <div className="settings-page__content-item">
- <div className="settings-page__content-item-col">
- <Button
- type="secondary"
- large
- className="settings-tab__button--orange"
- onClick={event => {
- event.preventDefault()
- setFeatureFlagToBeta()
- }}
- >
- { t('useOldUI') }
- </Button>
- </div>
- </div>
- </div>
- )
- }
-
- renderResetAccount () {
- const { t } = this.context
- const { showResetAccountConfirmationModal } = this.props
-
- return (
- <div className="settings-page__content-row">
- <div className="settings-page__content-item">
- <span>{ t('resetAccount') }</span>
- </div>
- <div className="settings-page__content-item">
- <div className="settings-page__content-item-col">
- <Button
- type="secondary"
- large
- className="settings-tab__button--orange"
- onClick={event => {
- event.preventDefault()
- showResetAccountConfirmationModal()
- }}
- >
- { t('resetAccount') }
- </Button>
- </div>
- </div>
- </div>
- )
- }
-
- renderBlockieOptIn () {
- const { useBlockie, setUseBlockie } = this.props
-
- return (
- <div className="settings-page__content-row">
- <div className="settings-page__content-item">
- <span>{ this.context.t('blockiesIdenticon') }</span>
- </div>
- <div className="settings-page__content-item">
- <div className="settings-page__content-item-col">
- <ToggleButton
- value={useBlockie}
- onToggle={value => setUseBlockie(!value)}
- activeLabel=""
- inactiveLabel=""
- />
- </div>
- </div>
- </div>
- )
- }
-
- renderHexDataOptIn () {
- const { t } = this.context
- const { sendHexData, setHexDataFeatureFlag } = this.props
-
- return (
- <div className="settings-page__content-row">
- <div className="settings-page__content-item">
- <span>{ t('showHexData') }</span>
- <div className="settings-page__content-description">
- { t('showHexDataDescription') }
- </div>
- </div>
- <div className="settings-page__content-item">
- <div className="settings-page__content-item-col">
- <ToggleButton
- value={sendHexData}
- onToggle={value => setHexDataFeatureFlag(!value)}
- activeLabel=""
- inactiveLabel=""
- />
- </div>
- </div>
- </div>
- )
- }
-
- renderUsePrimaryCurrencyOptions () {
- const { t } = this.context
- const {
- nativeCurrency,
- setUseNativeCurrencyAsPrimaryCurrencyPreference,
- useNativeCurrencyAsPrimaryCurrency,
- } = this.props
-
- return (
- <div className="settings-page__content-row">
- <div className="settings-page__content-item">
- <span>{ t('primaryCurrencySetting') }</span>
- <div className="settings-page__content-description">
- { t('primaryCurrencySettingDescription') }
- </div>
- </div>
- <div className="settings-page__content-item">
- <div className="settings-page__content-item-col">
- <div className="settings-tab__radio-buttons">
- <div className="settings-tab__radio-button">
- <input
- type="radio"
- id="native-primary-currency"
- onChange={() => setUseNativeCurrencyAsPrimaryCurrencyPreference(true)}
- checked={Boolean(useNativeCurrencyAsPrimaryCurrency)}
- />
- <label
- htmlFor="native-primary-currency"
- className="settings-tab__radio-label"
- >
- { nativeCurrency }
- </label>
- </div>
- <div className="settings-tab__radio-button">
- <input
- type="radio"
- id="fiat-primary-currency"
- onChange={() => setUseNativeCurrencyAsPrimaryCurrencyPreference(false)}
- checked={!useNativeCurrencyAsPrimaryCurrency}
- />
- <label
- htmlFor="fiat-primary-currency"
- className="settings-tab__radio-label"
- >
- { t('fiat') }
- </label>
- </div>
- </div>
- </div>
- </div>
- </div>
- )
- }
-
- renderPrivacyOptIn () {
- const { t } = this.context
- const { privacyMode, setPrivacyMode } = this.props
-
- return (
- <div className="settings-page__content-row">
- <div className="settings-page__content-item">
- <span>{ t('privacyMode') }</span>
- <div className="settings-page__content-description">
- { t('privacyModeDescription') }
- </div>
- </div>
- <div className="settings-page__content-item">
- <div className="settings-page__content-item-col">
- <ToggleButton
- value={privacyMode}
- onToggle={value => setPrivacyMode(!value)}
- activeLabel=""
- inactiveLabel=""
- />
- </div>
- </div>
- </div>
- )
- }
-
- render () {
- const { warning, isMascara } = this.props
-
- return (
- <div className="settings-page__content">
- { warning && <div className="settings-tab__error">{ warning }</div> }
- { this.renderCurrentConversion() }
- { this.renderUsePrimaryCurrencyOptions() }
- { this.renderCurrentLocale() }
- { this.renderNewRpcUrl() }
- { this.renderStateLogs() }
- { this.renderSeedWords() }
- { !isMascara && this.renderOldUI() }
- { this.renderResetAccount() }
- { this.renderClearApproval() }
- { this.renderPrivacyOptIn() }
- { this.renderHexDataOptIn() }
- { this.renderBlockieOptIn() }
- </div>
- )
- }
-}
diff --git a/ui/app/components/pages/settings/settings-tab/settings-tab.container.js b/ui/app/components/pages/settings/settings-tab/settings-tab.container.js
deleted file mode 100644
index b6c33a5b2..000000000
--- a/ui/app/components/pages/settings/settings-tab/settings-tab.container.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import SettingsTab from './settings-tab.component'
-import { compose } from 'recompose'
-import { connect } from 'react-redux'
-import { withRouter } from 'react-router-dom'
-import {
- setCurrentCurrency,
- setRpcTarget,
- displayWarning,
- revealSeedConfirmation,
- setUseBlockie,
- updateCurrentLocale,
- setFeatureFlag,
- showModal,
- setUseNativeCurrencyAsPrimaryCurrencyPreference,
-} from '../../../../actions'
-import { preferencesSelector } from '../../../../selectors'
-
-const mapStateToProps = state => {
- const { appState: { warning }, metamask } = state
- const {
- currentCurrency,
- conversionDate,
- nativeCurrency,
- useBlockie,
- featureFlags: {
- sendHexData,
- privacyMode,
- } = {},
- provider = {},
- isMascara,
- currentLocale,
- } = metamask
- const { useNativeCurrencyAsPrimaryCurrency } = preferencesSelector(state)
-
- return {
- warning,
- isMascara,
- currentLocale,
- currentCurrency,
- conversionDate,
- nativeCurrency,
- useBlockie,
- sendHexData,
- privacyMode,
- provider,
- useNativeCurrencyAsPrimaryCurrency,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- setCurrentCurrency: currency => dispatch(setCurrentCurrency(currency)),
- setRpcTarget: (newRpc, chainId, ticker, nickname) => dispatch(setRpcTarget(newRpc, chainId, ticker, nickname)),
- displayWarning: warning => dispatch(displayWarning(warning)),
- revealSeedConfirmation: () => dispatch(revealSeedConfirmation()),
- setUseBlockie: value => dispatch(setUseBlockie(value)),
- updateCurrentLocale: key => dispatch(updateCurrentLocale(key)),
- setFeatureFlagToBeta: () => {
- return dispatch(setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
- },
- setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
- setPrivacyMode: enabled => dispatch(setFeatureFlag('privacyMode', enabled)),
- showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),
- setUseNativeCurrencyAsPrimaryCurrencyPreference: value => {
- return dispatch(setUseNativeCurrencyAsPrimaryCurrencyPreference(value))
- },
- showClearApprovalModal: () => dispatch(showModal({ name: 'CLEAR_APPROVED_ORIGINS' })),
- }
-}
-
-export default compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(SettingsTab)
diff --git a/ui/app/components/pages/settings/settings.component.js b/ui/app/components/pages/settings/settings.component.js
deleted file mode 100644
index 94a97bba1..000000000
--- a/ui/app/components/pages/settings/settings.component.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import { Switch, Route, matchPath } from 'react-router-dom'
-import TabBar from '../../tab-bar'
-import SettingsTab from './settings-tab'
-import InfoTab from './info-tab'
-import { DEFAULT_ROUTE, SETTINGS_ROUTE, INFO_ROUTE } from '../../../routes'
-
-export default class SettingsPage extends PureComponent {
- static propTypes = {
- location: PropTypes.object,
- history: PropTypes.object,
- t: PropTypes.func,
- }
-
- static contextTypes = {
- t: PropTypes.func,
- }
-
- render () {
- const { history, location } = this.props
-
- return (
- <div className="main-container settings-page">
- <div className="settings-page__header">
- <div
- className="settings-page__close-button"
- onClick={() => history.push(DEFAULT_ROUTE)}
- />
- <TabBar
- tabs={[
- { content: this.context.t('settings'), key: SETTINGS_ROUTE },
- { content: this.context.t('info'), key: INFO_ROUTE },
- ]}
- isActive={key => matchPath(location.pathname, { path: key, exact: true })}
- onSelect={key => history.push(key)}
- />
- </div>
- <Switch>
- <Route
- exact
- path={INFO_ROUTE}
- component={InfoTab}
- />
- <Route
- exact
- path={SETTINGS_ROUTE}
- component={SettingsTab}
- />
- </Switch>
- </div>
- )
- }
-}
diff --git a/ui/app/components/pages/unlock-page/index.js b/ui/app/components/pages/unlock-page/index.js
deleted file mode 100644
index be80cde4f..000000000
--- a/ui/app/components/pages/unlock-page/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import UnlockPage from './unlock-page.container'
-module.exports = UnlockPage
diff --git a/ui/app/components/pages/unlock-page/index.scss b/ui/app/components/pages/unlock-page/index.scss
deleted file mode 100644
index 6bd52282d..000000000
--- a/ui/app/components/pages/unlock-page/index.scss
+++ /dev/null
@@ -1,52 +0,0 @@
-.unlock-page {
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
- align-items: center;
- width: 357px;
- padding: 30px;
- font-weight: 400;
- color: $silver-chalice;
-
- &__container {
- background: $white;
- display: flex;
- align-self: stretch;
- justify-content: center;
- flex: 1 0 auto;
- height: 100vh;
- }
-
- &__mascot-container {
- margin-top: 24px;
- }
-
- &__title {
- margin-top: 5px;
- font-size: 2rem;
- font-weight: 800;
- color: $tundora;
- }
-
- &__form {
- width: 100%;
- margin: 56px 0 8px;
- }
-
- &__links {
- margin-top: 25px;
- width: 100%;
- }
-
- &__link {
- cursor: pointer;
-
- &--import {
- color: $ecstasy;
- }
-
- &--use-classic {
- margin-top: 10px;
- }
- }
-}
diff --git a/ui/app/components/pages/unlock-page/unlock-page.component.js b/ui/app/components/pages/unlock-page/unlock-page.component.js
deleted file mode 100644
index 94915df76..000000000
--- a/ui/app/components/pages/unlock-page/unlock-page.component.js
+++ /dev/null
@@ -1,179 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import Button from '@material-ui/core/Button'
-import TextField from '../../text-field'
-import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
-import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
-import getCaretCoordinates from 'textarea-caret'
-import { EventEmitter } from 'events'
-import Mascot from '../../mascot'
-import { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } from '../../../routes'
-
-export default class UnlockPage extends Component {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- forgotPassword: PropTypes.func,
- tryUnlockMetamask: PropTypes.func,
- markPasswordForgotten: PropTypes.func,
- history: PropTypes.object,
- isUnlocked: PropTypes.bool,
- useOldInterface: PropTypes.func,
- }
-
- constructor (props) {
- super(props)
-
- this.state = {
- password: '',
- error: null,
- }
-
- this.submitting = false
- this.animationEventEmitter = new EventEmitter()
- }
-
- componentWillMount () {
- const { isUnlocked, history } = this.props
-
- if (isUnlocked) {
- history.push(DEFAULT_ROUTE)
- }
- }
-
- async handleSubmit (event) {
- event.preventDefault()
- event.stopPropagation()
-
- const { password } = this.state
- const { tryUnlockMetamask, history } = this.props
-
- if (password === '' || this.submitting) {
- return
- }
-
- this.setState({ error: null })
- this.submitting = true
-
- try {
- await tryUnlockMetamask(password)
- this.submitting = false
- history.push(DEFAULT_ROUTE)
- } catch ({ message }) {
- this.setState({ error: message })
- this.submitting = false
- }
- }
-
- handleInputChange ({ target }) {
- this.setState({ password: target.value, error: null })
-
- // tell mascot to look at page action
- const element = target
- const boundingRect = element.getBoundingClientRect()
- const coordinates = getCaretCoordinates(element, element.selectionEnd)
- this.animationEventEmitter.emit('point', {
- x: boundingRect.left + coordinates.left - element.scrollLeft,
- y: boundingRect.top + coordinates.top - element.scrollTop,
- })
- }
-
- renderSubmitButton () {
- const style = {
- backgroundColor: '#f7861c',
- color: 'white',
- marginTop: '20px',
- height: '60px',
- fontWeight: '400',
- boxShadow: 'none',
- borderRadius: '4px',
- }
-
- return (
- <Button
- type="submit"
- style={style}
- disabled={!this.state.password}
- fullWidth
- variant="raised"
- size="large"
- onClick={event => this.handleSubmit(event)}
- disableRipple
- >
- { this.context.t('login') }
- </Button>
- )
- }
-
- render () {
- const { password, error } = this.state
- const { t } = this.context
- const { markPasswordForgotten, history } = this.props
-
- return (
- <div className="unlock-page__container">
- <div className="unlock-page">
- <div className="unlock-page__mascot-container">
- <Mascot
- animationEventEmitter={this.animationEventEmitter}
- width="120"
- height="120"
- />
- </div>
- <h1 className="unlock-page__title">
- { t('welcomeBack') }
- </h1>
- <div>{ t('unlockMessage') }</div>
- <form
- className="unlock-page__form"
- onSubmit={event => this.handleSubmit(event)}
- >
- <TextField
- id="password"
- label={t('password')}
- type="password"
- value={password}
- onChange={event => this.handleInputChange(event)}
- error={error}
- autoFocus
- autoComplete="current-password"
- material
- fullWidth
- />
- </form>
- { this.renderSubmitButton() }
- <div className="unlock-page__links">
- <div
- className="unlock-page__link"
- onClick={() => {
- markPasswordForgotten()
- history.push(RESTORE_VAULT_ROUTE)
-
- if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
- global.platform.openExtensionInBrowser()
- }
- }}
- >
- { t('restoreFromSeed') }
- </div>
- <div
- className="unlock-page__link unlock-page__link--import"
- onClick={() => {
- markPasswordForgotten()
- history.push(RESTORE_VAULT_ROUTE)
-
- if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
- global.platform.openExtensionInBrowser()
- }
- }}
- >
- { t('importUsingSeed') }
- </div>
- </div>
- </div>
- </div>
- )
- }
-}
diff --git a/ui/app/components/pages/unlock-page/unlock-page.container.js b/ui/app/components/pages/unlock-page/unlock-page.container.js
deleted file mode 100644
index 18fed9b2e..000000000
--- a/ui/app/components/pages/unlock-page/unlock-page.container.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import { connect } from 'react-redux'
-import { withRouter } from 'react-router-dom'
-import { compose } from 'recompose'
-
-const {
- tryUnlockMetamask,
- forgotPassword,
- markPasswordForgotten,
-} = require('../../../actions')
-
-import UnlockPage from './unlock-page.component'
-
-const mapStateToProps = state => {
- const { metamask: { isUnlocked } } = state
- return {
- isUnlocked,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- forgotPassword: () => dispatch(forgotPassword()),
- tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)),
- markPasswordForgotten: () => dispatch(markPasswordForgotten()),
- }
-}
-
-export default compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(UnlockPage)
diff --git a/ui/app/components/send/account-list-item/account-list-item.component.js b/ui/app/components/send/account-list-item/account-list-item.component.js
deleted file mode 100644
index 14bb7471f..000000000
--- a/ui/app/components/send/account-list-item/account-list-item.component.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import { checksumAddress } from '../../../util'
-import Identicon from '../../identicon'
-import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
-import { PRIMARY, SECONDARY } from '../../../constants/common'
-
-export default class AccountListItem extends Component {
-
- static propTypes = {
- account: PropTypes.object,
- className: PropTypes.string,
- conversionRate: PropTypes.number,
- currentCurrency: PropTypes.string,
- displayAddress: PropTypes.bool,
- displayBalance: PropTypes.bool,
- handleClick: PropTypes.func,
- icon: PropTypes.node,
- };
-
- static contextTypes = {
- t: PropTypes.func,
- };
-
- render () {
- const {
- account,
- className,
- displayAddress = false,
- displayBalance = true,
- handleClick,
- icon = null,
- } = this.props
-
- const { name, address, balance } = account || {}
-
- return (<div
- className={`account-list-item ${className}`}
- onClick={() => handleClick({ name, address, balance })}
- >
-
- <div className="account-list-item__top-row">
- <Identicon
- address={address}
- className="account-list-item__identicon"
- diameter={18}
- />
-
- <div className="account-list-item__account-name">{ name || address }</div>
-
- {icon && <div className="account-list-item__icon">{ icon }</div>}
-
- </div>
-
- {displayAddress && name && <div className="account-list-item__account-address">
- { checksumAddress(address) }
- </div>}
-
- {
- displayBalance && (
- <div className="account-list-item__account-balances">
- <UserPreferencedCurrencyDisplay
- type={PRIMARY}
- value={balance}
- />
- <UserPreferencedCurrencyDisplay
- type={SECONDARY}
- value={balance}
- />
- </div>
- )
- }
-
- </div>)
- }
-}
diff --git a/ui/app/components/send/account-list-item/tests/account-list-item-container.test.js b/ui/app/components/send/account-list-item/tests/account-list-item-container.test.js
deleted file mode 100644
index 7c2f5fcb2..000000000
--- a/ui/app/components/send/account-list-item/tests/account-list-item-container.test.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import assert from 'assert'
-import proxyquire from 'proxyquire'
-
-let mapStateToProps
-
-proxyquire('../account-list-item.container.js', {
- 'react-redux': {
- connect: (ms, md) => {
- mapStateToProps = ms
- return () => ({})
- },
- },
- '../send.selectors.js': {
- getConversionRate: (s) => `mockConversionRate:${s}`,
- getCurrentCurrency: (s) => `mockCurrentCurrency:${s}`,
- getNativeCurrency: (s) => `mockNativeCurrency:${s}`,
- },
-})
-
-describe('account-list-item container', () => {
-
- describe('mapStateToProps()', () => {
-
- it('should map the correct properties to props', () => {
- assert.deepEqual(mapStateToProps('mockState'), {
- conversionRate: 'mockConversionRate:mockState',
- currentCurrency: 'mockCurrentCurrency:mockState',
- nativeCurrency: 'mockNativeCurrency:mockState',
- })
- })
-
- })
-
-})
diff --git a/ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js b/ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
deleted file mode 100644
index b490a7fd7..000000000
--- a/ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
+++ /dev/null
@@ -1,22 +0,0 @@
-const {
- multiplyCurrencies,
- subtractCurrencies,
-} = require('../../../../../conversion-util')
-const ethUtil = require('ethereumjs-util')
-
-function calcMaxAmount ({ balance, gasTotal, selectedToken, tokenBalance }) {
- const { decimals } = selectedToken || {}
- const multiplier = Math.pow(10, Number(decimals || 0))
-
- return selectedToken
- ? multiplyCurrencies(tokenBalance, multiplier, {toNumericBase: 'hex'})
- : subtractCurrencies(
- ethUtil.addHexPrefix(balance),
- ethUtil.addHexPrefix(gasTotal),
- { toNumericBase: 'hex' }
- )
-}
-
-module.exports = {
- calcMaxAmount,
-}
diff --git a/ui/app/components/send/send-content/send-content-README.md b/ui/app/components/send/send-content/send-content-README.md
deleted file mode 100644
index e69de29bb..000000000
--- a/ui/app/components/send/send-content/send-content-README.md
+++ /dev/null
diff --git a/ui/app/components/send/send-content/send-content.scss b/ui/app/components/send/send-content/send-content.scss
deleted file mode 100644
index e69de29bb..000000000
--- a/ui/app/components/send/send-content/send-content.scss
+++ /dev/null
diff --git a/ui/app/components/send/send-content/send-from-row/from-dropdown/from-dropdown-README.md b/ui/app/components/send/send-content/send-from-row/from-dropdown/from-dropdown-README.md
deleted file mode 100644
index e69de29bb..000000000
--- a/ui/app/components/send/send-content/send-from-row/from-dropdown/from-dropdown-README.md
+++ /dev/null
diff --git a/ui/app/components/send/send-content/send-from-row/from-dropdown/from-dropdown.component.js b/ui/app/components/send/send-content/send-from-row/from-dropdown/from-dropdown.component.js
deleted file mode 100644
index 4f43a9d61..000000000
--- a/ui/app/components/send/send-content/send-from-row/from-dropdown/from-dropdown.component.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import AccountListItem from '../../../account-list-item/'
-import SendDropdownList from '../../send-dropdown-list/'
-
-export default class FromDropdown extends Component {
-
- static propTypes = {
- accounts: PropTypes.array,
- closeDropdown: PropTypes.func,
- dropdownOpen: PropTypes.bool,
- onSelect: PropTypes.func,
- openDropdown: PropTypes.func,
- selectedAccount: PropTypes.object,
- };
-
- static contextTypes = {
- t: PropTypes.func,
- };
-
- render () {
- const {
- accounts,
- closeDropdown,
- dropdownOpen,
- openDropdown,
- selectedAccount,
- onSelect,
- } = this.props
-
- return <div className="send-v2__from-dropdown">
- <AccountListItem
- account={selectedAccount}
- handleClick={openDropdown}
- icon={<i className={`fa fa-caret-down fa-lg`} style={ { color: '#dedede' } }/>}
- />
- {dropdownOpen && <SendDropdownList
- accounts={accounts}
- closeDropdown={closeDropdown}
- onSelect={onSelect}
- activeAddress={selectedAccount.address}
- />}
- </div>
- }
-
-}
diff --git a/ui/app/components/send/send-content/send-from-row/from-dropdown/from-dropdown.scss b/ui/app/components/send/send-content/send-from-row/from-dropdown/from-dropdown.scss
deleted file mode 100644
index e69de29bb..000000000
--- a/ui/app/components/send/send-content/send-from-row/from-dropdown/from-dropdown.scss
+++ /dev/null
diff --git a/ui/app/components/send/send-content/send-from-row/from-dropdown/index.js b/ui/app/components/send/send-content/send-from-row/from-dropdown/index.js
deleted file mode 100644
index 2314ef4e3..000000000
--- a/ui/app/components/send/send-content/send-from-row/from-dropdown/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './from-dropdown.component'
diff --git a/ui/app/components/send/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js b/ui/app/components/send/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js
deleted file mode 100644
index 84fcb281e..000000000
--- a/ui/app/components/send/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import React from 'react'
-import assert from 'assert'
-import { shallow } from 'enzyme'
-import sinon from 'sinon'
-import FromDropdown from '../from-dropdown.component.js'
-
-import AccountListItem from '../../../../account-list-item/account-list-item.container'
-import SendDropdownList from '../../../send-dropdown-list/send-dropdown-list.component'
-
-const propsMethodSpies = {
- closeDropdown: sinon.spy(),
- openDropdown: sinon.spy(),
- onSelect: sinon.spy(),
-}
-
-describe('FromDropdown Component', function () {
- let wrapper
-
- beforeEach(() => {
- wrapper = shallow(<FromDropdown
- accounts={['mockAccount']}
- closeDropdown={propsMethodSpies.closeDropdown}
- dropdownOpen={false}
- onSelect={propsMethodSpies.onSelect}
- openDropdown={propsMethodSpies.openDropdown}
- selectedAccount={ { address: 'mockAddress' } }
- />, { context: { t: str => str + '_t' } })
- })
-
- afterEach(() => {
- propsMethodSpies.closeDropdown.resetHistory()
- propsMethodSpies.openDropdown.resetHistory()
- propsMethodSpies.onSelect.resetHistory()
- })
-
- describe('render', () => {
- it('should render a div with a .send-v2__from-dropdown class', () => {
- assert.equal(wrapper.find('.send-v2__from-dropdown').length, 1)
- })
-
- it('should render an AccountListItem as the first child of the .send-v2__from-dropdown div', () => {
- assert(wrapper.find('.send-v2__from-dropdown').childAt(0).is(AccountListItem))
- })
-
- it('should pass the correct props to AccountListItem', () => {
- const {
- account,
- handleClick,
- icon,
- } = wrapper.find('.send-v2__from-dropdown').childAt(0).props()
- assert.deepEqual(account, { address: 'mockAddress' })
- assert.deepEqual(
- icon,
- <i className={`fa fa-caret-down fa-lg`} style={ { color: '#dedede' } }/>
- )
- assert.equal(propsMethodSpies.openDropdown.callCount, 0)
- handleClick()
- assert.equal(propsMethodSpies.openDropdown.callCount, 1)
- })
-
- it('should not render a SendDropdownList when dropdownOpen is false', () => {
- assert.equal(wrapper.find(SendDropdownList).length, 0)
- })
-
- it('should render a SendDropdownList when dropdownOpen is true', () => {
- wrapper.setProps({ dropdownOpen: true })
- assert(wrapper.find(SendDropdownList).length, 1)
- })
-
- it('should pass the correct props to the SendDropdownList]', () => {
- wrapper.setProps({ dropdownOpen: true })
- const {
- accounts,
- closeDropdown,
- onSelect,
- activeAddress,
- } = wrapper.find(SendDropdownList).props()
- assert.deepEqual(accounts, ['mockAccount'])
- assert.equal(activeAddress, 'mockAddress')
- assert.equal(propsMethodSpies.closeDropdown.callCount, 0)
- closeDropdown()
- assert.equal(propsMethodSpies.closeDropdown.callCount, 1)
- assert.equal(propsMethodSpies.onSelect.callCount, 0)
- onSelect()
- assert.equal(propsMethodSpies.onSelect.callCount, 1)
- })
- })
-})
diff --git a/ui/app/components/send/send-content/send-from-row/send-from-row-README.md b/ui/app/components/send/send-content/send-from-row/send-from-row-README.md
deleted file mode 100644
index e69de29bb..000000000
--- a/ui/app/components/send/send-content/send-from-row/send-from-row-README.md
+++ /dev/null
diff --git a/ui/app/components/send/send-content/send-from-row/send-from-row.component.js b/ui/app/components/send/send-content/send-from-row/send-from-row.component.js
deleted file mode 100644
index 3e0e0de22..000000000
--- a/ui/app/components/send/send-content/send-from-row/send-from-row.component.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import SendRowWrapper from '../send-row-wrapper/'
-import FromDropdown from './from-dropdown/'
-
-export default class SendFromRow extends Component {
-
- static propTypes = {
- closeFromDropdown: PropTypes.func,
- conversionRate: PropTypes.number,
- from: PropTypes.object,
- fromAccounts: PropTypes.array,
- fromDropdownOpen: PropTypes.bool,
- openFromDropdown: PropTypes.func,
- tokenContract: PropTypes.object,
- updateSendFrom: PropTypes.func,
- setSendTokenBalance: PropTypes.func,
- };
-
- static contextTypes = {
- t: PropTypes.func,
- };
-
- async handleFromChange (newFrom) {
- const {
- updateSendFrom,
- tokenContract,
- setSendTokenBalance,
- } = this.props
-
- if (tokenContract) {
- const usersToken = await tokenContract.balanceOf(newFrom.address)
- setSendTokenBalance(usersToken)
- }
- updateSendFrom(newFrom)
- }
-
- render () {
- const {
- closeFromDropdown,
- conversionRate,
- from,
- fromAccounts,
- fromDropdownOpen,
- openFromDropdown,
- } = this.props
-
- return (
- <SendRowWrapper label={`${this.context.t('from')}:`}>
- <FromDropdown
- accounts={fromAccounts}
- closeDropdown={() => closeFromDropdown()}
- conversionRate={conversionRate}
- dropdownOpen={fromDropdownOpen}
- onSelect={newFrom => this.handleFromChange(newFrom)}
- openDropdown={() => openFromDropdown()}
- selectedAccount={from}
- />
- </SendRowWrapper>
- )
- }
-
-}
diff --git a/ui/app/components/send/send-content/send-from-row/send-from-row.container.js b/ui/app/components/send/send-content/send-from-row/send-from-row.container.js
deleted file mode 100644
index 33cb63b43..000000000
--- a/ui/app/components/send/send-content/send-from-row/send-from-row.container.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import { connect } from 'react-redux'
-import {
- accountsWithSendEtherInfoSelector,
- getConversionRate,
- getSelectedTokenContract,
- getSendFromObject,
-} from '../../send.selectors.js'
-import {
- getFromDropdownOpen,
-} from './send-from-row.selectors.js'
-import { calcTokenBalance } from '../../send.utils.js'
-import {
- updateSendFrom,
- setSendTokenBalance,
-} from '../../../../actions'
-import {
- closeFromDropdown,
- openFromDropdown,
-} from '../../../../ducks/send.duck'
-import SendFromRow from './send-from-row.component'
-
-export default connect(mapStateToProps, mapDispatchToProps)(SendFromRow)
-
-function mapStateToProps (state) {
- return {
- conversionRate: getConversionRate(state),
- from: getSendFromObject(state),
- fromAccounts: accountsWithSendEtherInfoSelector(state),
- fromDropdownOpen: getFromDropdownOpen(state),
- tokenContract: getSelectedTokenContract(state),
- }
-}
-
-function mapDispatchToProps (dispatch) {
- return {
- closeFromDropdown: () => dispatch(closeFromDropdown()),
- openFromDropdown: () => dispatch(openFromDropdown()),
- updateSendFrom: newFrom => dispatch(updateSendFrom(newFrom)),
- setSendTokenBalance: (usersToken, selectedToken) => {
- if (!usersToken) return
-
- const tokenBalance = calcTokenBalance({ usersToken, selectedToken })
- dispatch(setSendTokenBalance(tokenBalance))
- },
- }
-}
diff --git a/ui/app/components/send/send-content/send-from-row/tests/send-from-row-component.test.js b/ui/app/components/send/send-content/send-from-row/tests/send-from-row-component.test.js
deleted file mode 100644
index 9ba8d1739..000000000
--- a/ui/app/components/send/send-content/send-from-row/tests/send-from-row-component.test.js
+++ /dev/null
@@ -1,121 +0,0 @@
-import React from 'react'
-import assert from 'assert'
-import { shallow } from 'enzyme'
-import sinon from 'sinon'
-import SendFromRow from '../send-from-row.component.js'
-
-import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
-import FromDropdown from '../from-dropdown/from-dropdown.component'
-
-const propsMethodSpies = {
- closeFromDropdown: sinon.spy(),
- openFromDropdown: sinon.spy(),
- updateSendFrom: sinon.spy(),
- setSendTokenBalance: sinon.spy(),
-}
-
-sinon.spy(SendFromRow.prototype, 'handleFromChange')
-
-describe('SendFromRow Component', function () {
- let wrapper
- let instance
-
- beforeEach(() => {
- wrapper = shallow(<SendFromRow
- closeFromDropdown={propsMethodSpies.closeFromDropdown}
- conversionRate={15}
- from={ { address: 'mockAddress' } }
- fromAccounts={['mockAccount']}
- fromDropdownOpen={false}
- openFromDropdown={propsMethodSpies.openFromDropdown}
- setSendTokenBalance={propsMethodSpies.setSendTokenBalance}
- tokenContract={null}
- updateSendFrom={propsMethodSpies.updateSendFrom}
- />, { context: { t: str => str + '_t' } })
- instance = wrapper.instance()
- })
-
- afterEach(() => {
- propsMethodSpies.closeFromDropdown.resetHistory()
- propsMethodSpies.openFromDropdown.resetHistory()
- propsMethodSpies.updateSendFrom.resetHistory()
- propsMethodSpies.setSendTokenBalance.resetHistory()
- SendFromRow.prototype.handleFromChange.resetHistory()
- })
-
- describe('handleFromChange', () => {
-
- it('should call updateSendFrom', () => {
- assert.equal(propsMethodSpies.updateSendFrom.callCount, 0)
- instance.handleFromChange('mockFrom')
- assert.equal(propsMethodSpies.updateSendFrom.callCount, 1)
- assert.deepEqual(
- propsMethodSpies.updateSendFrom.getCall(0).args,
- ['mockFrom']
- )
- })
-
- it('should call tokenContract.balanceOf and setSendTokenBalance if tokenContract is defined', async () => {
- wrapper.setProps({
- tokenContract: {
- balanceOf: () => new Promise((resolve) => resolve('mockUsersToken')),
- },
- })
- assert.equal(propsMethodSpies.setSendTokenBalance.callCount, 0)
- await instance.handleFromChange('mockFrom')
- assert.equal(propsMethodSpies.setSendTokenBalance.callCount, 1)
- assert.deepEqual(
- propsMethodSpies.setSendTokenBalance.getCall(0).args,
- ['mockUsersToken']
- )
- })
-
- })
-
- describe('render', () => {
- it('should render a SendRowWrapper component', () => {
- assert.equal(wrapper.find(SendRowWrapper).length, 1)
- })
-
- it('should pass the correct props to SendRowWrapper', () => {
- const {
- label,
- } = wrapper.find(SendRowWrapper).props()
-
- assert.equal(label, 'from_t:')
- })
-
- it('should render an FromDropdown as a child of the SendRowWrapper', () => {
- assert(wrapper.find(SendRowWrapper).childAt(0).is(FromDropdown))
- })
-
- it('should render the FromDropdown with the correct props', () => {
- const {
- accounts,
- closeDropdown,
- conversionRate,
- dropdownOpen,
- onSelect,
- openDropdown,
- selectedAccount,
- } = wrapper.find(SendRowWrapper).childAt(0).props()
- assert.deepEqual(accounts, ['mockAccount'])
- assert.equal(dropdownOpen, false)
- assert.equal(conversionRate, 15)
- assert.deepEqual(selectedAccount, { address: 'mockAddress' })
- assert.equal(propsMethodSpies.closeFromDropdown.callCount, 0)
- closeDropdown()
- assert.equal(propsMethodSpies.closeFromDropdown.callCount, 1)
- assert.equal(propsMethodSpies.openFromDropdown.callCount, 0)
- openDropdown()
- assert.equal(propsMethodSpies.openFromDropdown.callCount, 1)
- assert.equal(SendFromRow.prototype.handleFromChange.callCount, 0)
- onSelect('mockNewFrom')
- assert.equal(SendFromRow.prototype.handleFromChange.callCount, 1)
- assert.deepEqual(
- SendFromRow.prototype.handleFromChange.getCall(0).args,
- ['mockNewFrom']
- )
- })
- })
-})
diff --git a/ui/app/components/send/send-content/send-from-row/tests/send-from-row-container.test.js b/ui/app/components/send/send-content/send-from-row/tests/send-from-row-container.test.js
deleted file mode 100644
index e080b2fe3..000000000
--- a/ui/app/components/send/send-content/send-from-row/tests/send-from-row-container.test.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import assert from 'assert'
-import proxyquire from 'proxyquire'
-import sinon from 'sinon'
-
-let mapStateToProps
-let mapDispatchToProps
-
-const actionSpies = {
- updateSendFrom: sinon.spy(),
- setSendTokenBalance: sinon.spy(),
-}
-const duckActionSpies = {
- closeFromDropdown: sinon.spy(),
- openFromDropdown: sinon.spy(),
-}
-
-proxyquire('../send-from-row.container.js', {
- 'react-redux': {
- connect: (ms, md) => {
- mapStateToProps = ms
- mapDispatchToProps = md
- return () => ({})
- },
- },
- '../../send.selectors.js': {
- accountsWithSendEtherInfoSelector: (s) => `mockFromAccounts:${s}`,
- getConversionRate: (s) => `mockConversionRate:${s}`,
- getSelectedTokenContract: (s) => `mockTokenContract:${s}`,
- getSendFromObject: (s) => `mockFrom:${s}`,
- },
- './send-from-row.selectors.js': { getFromDropdownOpen: (s) => `mockFromDropdownOpen:${s}` },
- '../../send.utils.js': { calcTokenBalance: ({ usersToken, selectedToken }) => usersToken + selectedToken },
- '../../../../actions': actionSpies,
- '../../../../ducks/send.duck': duckActionSpies,
-})
-
-describe('send-from-row container', () => {
-
- describe('mapStateToProps()', () => {
-
- it('should map the correct properties to props', () => {
- assert.deepEqual(mapStateToProps('mockState'), {
- conversionRate: 'mockConversionRate:mockState',
- from: 'mockFrom:mockState',
- fromAccounts: 'mockFromAccounts:mockState',
- fromDropdownOpen: 'mockFromDropdownOpen:mockState',
- tokenContract: 'mockTokenContract:mockState',
- })
- })
-
- })
-
- describe('mapDispatchToProps()', () => {
- let dispatchSpy
- let mapDispatchToPropsObject
-
- beforeEach(() => {
- dispatchSpy = sinon.spy()
- mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
- })
-
- describe('closeFromDropdown()', () => {
- it('should dispatch a closeFromDropdown action', () => {
- mapDispatchToPropsObject.closeFromDropdown()
- assert(dispatchSpy.calledOnce)
- assert(duckActionSpies.closeFromDropdown.calledOnce)
- assert.equal(
- duckActionSpies.closeFromDropdown.getCall(0).args[0],
- undefined
- )
- })
- })
-
- describe('openFromDropdown()', () => {
- it('should dispatch a openFromDropdown action', () => {
- mapDispatchToPropsObject.openFromDropdown()
- assert(dispatchSpy.calledOnce)
- assert(duckActionSpies.openFromDropdown.calledOnce)
- assert.equal(
- duckActionSpies.openFromDropdown.getCall(0).args[0],
- undefined
- )
- })
- })
-
- describe('updateSendFrom()', () => {
- it('should dispatch an updateSendFrom action', () => {
- mapDispatchToPropsObject.updateSendFrom('mockFrom')
- assert(dispatchSpy.calledOnce)
- assert.equal(
- actionSpies.updateSendFrom.getCall(0).args[0],
- 'mockFrom'
- )
- })
- })
-
- describe('setSendTokenBalance()', () => {
- it('should dispatch an setSendTokenBalance action', () => {
- mapDispatchToPropsObject.setSendTokenBalance('mockUsersToken', 'mockSelectedToken')
- assert(dispatchSpy.calledOnce)
- assert.equal(
- actionSpies.setSendTokenBalance.getCall(0).args[0],
- 'mockUsersTokenmockSelectedToken'
- )
- })
- })
-
- })
-
-})
diff --git a/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js
deleted file mode 100644
index 91b58cfd0..000000000
--- a/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js
+++ /dev/null
@@ -1,48 +0,0 @@
-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'
-
-export default class SendGasRow extends Component {
-
- static propTypes = {
- conversionRate: PropTypes.number,
- convertedCurrency: PropTypes.string,
- gasFeeError: PropTypes.bool,
- gasLoadingError: PropTypes.bool,
- gasTotal: PropTypes.string,
- showCustomizeGasModal: PropTypes.func,
- };
-
- static contextTypes = {
- t: PropTypes.func,
- };
-
- render () {
- const {
- conversionRate,
- convertedCurrency,
- gasLoadingError,
- gasTotal,
- gasFeeError,
- showCustomizeGasModal,
- } = this.props
-
- return (
- <SendRowWrapper
- label={`${this.context.t('gasFee')}:`}
- showError={gasFeeError}
- errorType={'gasFee'}
- >
- <GasFeeDisplay
- conversionRate={conversionRate}
- convertedCurrency={convertedCurrency}
- gasLoadingError={gasLoadingError}
- gasTotal={gasTotal}
- onClick={() => showCustomizeGasModal()}
- />
- </SendRowWrapper>
- )
- }
-
-}
diff --git a/ui/app/components/send/send-content/send-gas-row/send-gas-row.container.js b/ui/app/components/send/send-content/send-gas-row/send-gas-row.container.js
deleted file mode 100644
index 8f8e3e4dd..000000000
--- a/ui/app/components/send/send-content/send-gas-row/send-gas-row.container.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { connect } from 'react-redux'
-import {
- getConversionRate,
- getCurrentCurrency,
- getGasTotal,
-} from '../../send.selectors.js'
-import { getGasLoadingError, gasFeeIsInError } from './send-gas-row.selectors.js'
-import { showModal } from '../../../../actions'
-import SendGasRow from './send-gas-row.component'
-
-export default connect(mapStateToProps, mapDispatchToProps)(SendGasRow)
-
-function mapStateToProps (state) {
- return {
- conversionRate: getConversionRate(state),
- convertedCurrency: getCurrentCurrency(state),
- gasTotal: getGasTotal(state),
- gasFeeError: gasFeeIsInError(state),
- gasLoadingError: getGasLoadingError(state),
- }
-}
-
-function mapDispatchToProps (dispatch) {
- return {
- showCustomizeGasModal: () => dispatch(showModal({ name: 'CUSTOMIZE_GAS' })),
- }
-}
diff --git a/ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-container.test.js b/ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
deleted file mode 100644
index 2ce062505..000000000
--- a/ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import assert from 'assert'
-import proxyquire from 'proxyquire'
-import sinon from 'sinon'
-
-let mapStateToProps
-let mapDispatchToProps
-
-const actionSpies = {
- showModal: sinon.spy(),
-}
-
-proxyquire('../send-gas-row.container.js', {
- 'react-redux': {
- connect: (ms, md) => {
- mapStateToProps = ms
- mapDispatchToProps = md
- return () => ({})
- },
- },
- '../../send.selectors.js': {
- getConversionRate: (s) => `mockConversionRate:${s}`,
- getCurrentCurrency: (s) => `mockConvertedCurrency:${s}`,
- getGasTotal: (s) => `mockGasTotal:${s}`,
- },
- './send-gas-row.selectors.js': {
- getGasLoadingError: (s) => `mockGasLoadingError:${s}`,
- gasFeeIsInError: (s) => `mockGasFeeError:${s}`,
- },
- '../../../../actions': actionSpies,
-})
-
-describe('send-gas-row container', () => {
-
- describe('mapStateToProps()', () => {
-
- it('should map the correct properties to props', () => {
- assert.deepEqual(mapStateToProps('mockState'), {
- conversionRate: 'mockConversionRate:mockState',
- convertedCurrency: 'mockConvertedCurrency:mockState',
- gasTotal: 'mockGasTotal:mockState',
- gasFeeError: 'mockGasFeeError:mockState',
- gasLoadingError: 'mockGasLoadingError:mockState',
- })
- })
-
- })
-
- describe('mapDispatchToProps()', () => {
- let dispatchSpy
- let mapDispatchToPropsObject
-
- beforeEach(() => {
- dispatchSpy = sinon.spy()
- mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
- })
-
- describe('showCustomizeGasModal()', () => {
- it('should dispatch an action', () => {
- mapDispatchToPropsObject.showCustomizeGasModal()
- assert(dispatchSpy.calledOnce)
- assert.deepEqual(
- actionSpies.showModal.getCall(0).args[0],
- { name: 'CUSTOMIZE_GAS' }
- )
- })
- })
-
- })
-
-})
diff --git a/ui/app/components/send/send-content/send-to-row/send-to-row.utils.js b/ui/app/components/send/send-content/send-to-row/send-to-row.utils.js
deleted file mode 100644
index 0eeaa3a11..000000000
--- a/ui/app/components/send/send-content/send-to-row/send-to-row.utils.js
+++ /dev/null
@@ -1,21 +0,0 @@
-const {
- REQUIRED_ERROR,
- INVALID_RECIPIENT_ADDRESS_ERROR,
-} = require('../../send.constants')
-const { isValidAddress } = require('../../../../util')
-
-function getToErrorObject (to, toError = null, hasHexData = false) {
- if (!to) {
- if (!hasHexData) {
- toError = REQUIRED_ERROR
- }
- } else if (!isValidAddress(to) && !toError) {
- toError = INVALID_RECIPIENT_ADDRESS_ERROR
- }
-
- return { to: toError }
-}
-
-module.exports = {
- getToErrorObject,
-}
diff --git a/ui/app/components/send/send-content/send-to-row/tests/send-to-row-utils.test.js b/ui/app/components/send/send-content/send-to-row/tests/send-to-row-utils.test.js
deleted file mode 100644
index c779aeb76..000000000
--- a/ui/app/components/send/send-content/send-to-row/tests/send-to-row-utils.test.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import assert from 'assert'
-import proxyquire from 'proxyquire'
-import sinon from 'sinon'
-
-import {
- REQUIRED_ERROR,
- INVALID_RECIPIENT_ADDRESS_ERROR,
-} from '../../../send.constants'
-
-const stubs = {
- isValidAddress: sinon.stub().callsFake(to => Boolean(to.match(/^[0xabcdef123456798]+$/))),
-}
-
-const toRowUtils = proxyquire('../send-to-row.utils.js', {
- '../../../../util': {
- isValidAddress: stubs.isValidAddress,
- },
-})
-const {
- getToErrorObject,
-} = toRowUtils
-
-describe('send-to-row utils', () => {
-
- describe('getToErrorObject()', () => {
- it('should return a required error if to is falsy', () => {
- assert.deepEqual(getToErrorObject(null), {
- to: REQUIRED_ERROR,
- })
- })
-
- it('should return null if to is falsy and hexData is truthy', () => {
- assert.deepEqual(getToErrorObject(null, undefined, true), {
- to: null,
- })
- })
-
- it('should return an invalid recipient error if to is truthy but invalid', () => {
- assert.deepEqual(getToErrorObject('mockInvalidTo'), {
- to: INVALID_RECIPIENT_ADDRESS_ERROR,
- })
- })
-
- it('should return null if to is truthy and valid', () => {
- assert.deepEqual(getToErrorObject('0xabc123'), {
- to: null,
- })
- })
-
- it('should return the passed error if to is truthy but invalid if to is truthy and valid', () => {
- assert.deepEqual(getToErrorObject('invalid #$ 345878', 'someExplicitError'), {
- to: 'someExplicitError',
- })
- })
- })
-
-})
diff --git a/ui/app/components/sidebars/sidebar.component.js b/ui/app/components/sidebars/sidebar.component.js
deleted file mode 100644
index 57cdd7111..000000000
--- a/ui/app/components/sidebars/sidebar.component.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
-import WalletView from '../wallet-view'
-import { WALLET_VIEW_SIDEBAR } from './sidebar.constants'
-
-export default class Sidebar extends Component {
-
- static propTypes = {
- sidebarOpen: PropTypes.bool,
- hideSidebar: PropTypes.func,
- transitionName: PropTypes.string,
- type: PropTypes.string,
- };
-
- renderOverlay () {
- return <div className="sidebar-overlay" onClick={() => this.props.hideSidebar()} />
- }
-
- renderSidebarContent () {
- const { type } = this.props
-
- switch (type) {
- case WALLET_VIEW_SIDEBAR:
- return <WalletView responsiveDisplayClassname={'sidebar-right' } />
- default:
- return null
- }
-
- }
-
- render () {
- const { transitionName, sidebarOpen } = this.props
-
- return (
- <div>
- <ReactCSSTransitionGroup
- transitionName={transitionName}
- transitionEnterTimeout={300}
- transitionLeaveTimeout={200}
- >
- { sidebarOpen ? this.renderSidebarContent() : null }
- </ReactCSSTransitionGroup>
- { sidebarOpen ? this.renderOverlay() : null }
- </div>
- )
- }
-
-}
diff --git a/ui/app/components/tab-bar.js b/ui/app/components/tab-bar.js
deleted file mode 100644
index 0016a09c1..000000000
--- a/ui/app/components/tab-bar.js
+++ /dev/null
@@ -1,33 +0,0 @@
-const { Component } = require('react')
-const h = require('react-hyperscript')
-const PropTypes = require('prop-types')
-const classnames = require('classnames')
-
-class TabBar extends Component {
- render () {
- const { tabs = [], onSelect, isActive } = this.props
-
- return (
- h('.tab-bar', {}, [
- tabs.map(({ key, content }) => {
- return h('div', {
- className: classnames('tab-bar__tab pointer', {
- 'tab-bar__tab--active': isActive(key, content),
- }),
- onClick: () => onSelect(key),
- key,
- }, content)
- }),
- h('div.tab-bar__tab.tab-bar__grow-tab'),
- ])
- )
- }
-}
-
-TabBar.propTypes = {
- isActive: PropTypes.func.isRequired,
- tabs: PropTypes.array,
- onSelect: PropTypes.func,
-}
-
-module.exports = TabBar
diff --git a/ui/app/components/token-input/tests/token-input.container.test.js b/ui/app/components/token-input/tests/token-input.container.test.js
deleted file mode 100644
index d73bc9a94..000000000
--- a/ui/app/components/token-input/tests/token-input.container.test.js
+++ /dev/null
@@ -1,129 +0,0 @@
-import assert from 'assert'
-import proxyquire from 'proxyquire'
-
-let mapStateToProps, mergeProps
-
-proxyquire('../token-input.container.js', {
- 'react-redux': {
- connect: (ms, md, mp) => {
- mapStateToProps = ms
- mergeProps = mp
- return () => ({})
- },
- },
-})
-
-describe('TokenInput container', () => {
- describe('mapStateToProps()', () => {
- it('should return the correct props when send is empty', () => {
- const mockState = {
- metamask: {
- currentCurrency: 'usd',
- tokens: [
- {
- address: '0x1',
- decimals: '4',
- symbol: 'ABC',
- },
- ],
- selectedTokenAddress: '0x1',
- contractExchangeRates: {},
- send: {},
- },
- }
-
- assert.deepEqual(mapStateToProps(mockState), {
- currentCurrency: 'usd',
- selectedToken: {
- address: '0x1',
- decimals: '4',
- symbol: 'ABC',
- },
- selectedTokenExchangeRate: 0,
- })
- })
-
- it('should return the correct props when selectedTokenAddress is not found and send is populated', () => {
- const mockState = {
- metamask: {
- currentCurrency: 'usd',
- tokens: [
- {
- address: '0x1',
- decimals: '4',
- symbol: 'ABC',
- },
- ],
- selectedTokenAddress: '0x2',
- contractExchangeRates: {},
- send: {
- token: { address: 'test' },
- },
- },
- }
-
- assert.deepEqual(mapStateToProps(mockState), {
- currentCurrency: 'usd',
- selectedToken: {
- address: 'test',
- },
- selectedTokenExchangeRate: 0,
- })
- })
-
- it('should return the correct props when contractExchangeRates is populated', () => {
- const mockState = {
- metamask: {
- currentCurrency: 'usd',
- tokens: [
- {
- address: '0x1',
- decimals: '4',
- symbol: 'ABC',
- },
- ],
- selectedTokenAddress: '0x1',
- contractExchangeRates: {
- '0x1': 5,
- },
- send: {},
- },
- }
-
- assert.deepEqual(mapStateToProps(mockState), {
- currentCurrency: 'usd',
- selectedToken: {
- address: '0x1',
- decimals: '4',
- symbol: 'ABC',
- },
- selectedTokenExchangeRate: 5,
- })
- })
- })
-
- describe('mergeProps()', () => {
- it('should return the correct props', () => {
- const mockStateProps = {
- currentCurrency: 'usd',
- selectedToken: {
- address: '0x1',
- decimals: '4',
- symbol: 'ABC',
- },
- selectedTokenExchangeRate: 5,
- }
-
- assert.deepEqual(mergeProps(mockStateProps, {}, {}), {
- currentCurrency: 'usd',
- selectedToken: {
- address: '0x1',
- decimals: '4',
- symbol: 'ABC',
- },
- selectedTokenExchangeRate: 5,
- suffix: 'ABC',
- })
- })
- })
-})
diff --git a/ui/app/components/transaction-activity-log/tests/transaction-activity-log.component.test.js b/ui/app/components/transaction-activity-log/tests/transaction-activity-log.component.test.js
deleted file mode 100644
index 8687dbbc7..000000000
--- a/ui/app/components/transaction-activity-log/tests/transaction-activity-log.component.test.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from 'react'
-import assert from 'assert'
-import { shallow } from 'enzyme'
-import TransactionActivityLog from '../transaction-activity-log.component'
-import Card from '../../card'
-
-describe('TransactionActivityLog Component', () => {
- it('should render properly', () => {
- const transaction = {
- history: [],
- id: 1,
- status: 'confirmed',
- txParams: {
- from: '0x1',
- gas: '0x5208',
- gasPrice: '0x3b9aca00',
- nonce: '0xa4',
- to: '0x2',
- value: '0x2386f26fc10000',
- },
- }
-
- const wrapper = shallow(
- <TransactionActivityLog
- transaction={transaction}
- className="test-class"
- />,
- { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }
- )
-
- assert.ok(wrapper.hasClass('transaction-activity-log'))
- assert.ok(wrapper.hasClass('test-class'))
- assert.equal(wrapper.find(Card).length, 1)
- })
-})
diff --git a/ui/app/components/transaction-activity-log/tests/transaction-activity-log.util.test.js b/ui/app/components/transaction-activity-log/tests/transaction-activity-log.util.test.js
deleted file mode 100644
index 586500408..000000000
--- a/ui/app/components/transaction-activity-log/tests/transaction-activity-log.util.test.js
+++ /dev/null
@@ -1,208 +0,0 @@
-import assert from 'assert'
-import { getActivities } from '../transaction-activity-log.util'
-
-describe('getActivities', () => {
- it('should return no activities for an empty history', () => {
- const transaction = {
- history: [],
- id: 1,
- status: 'confirmed',
- txParams: {
- from: '0x1',
- gas: '0x5208',
- gasPrice: '0x3b9aca00',
- nonce: '0xa4',
- to: '0x2',
- value: '0x2386f26fc10000',
- },
- }
-
- assert.deepEqual(getActivities(transaction), [])
- })
-
- it('should return activities for a transaction\'s history', () => {
- const transaction = {
- history: [
- {
- id: 5559712943815343,
- loadingDefaults: true,
- metamaskNetworkId: '3',
- status: 'unapproved',
- time: 1535507561452,
- txParams: {
- from: '0x1',
- gas: '0x5208',
- gasPrice: '0x3b9aca00',
- nonce: '0xa4',
- to: '0x2',
- value: '0x2386f26fc10000',
- },
- },
- [
- {
- op: 'replace',
- path: '/loadingDefaults',
- timestamp: 1535507561515,
- value: false,
- },
- {
- op: 'add',
- path: '/gasPriceSpecified',
- value: true,
- },
- {
- op: 'add',
- path: '/gasLimitSpecified',
- value: true,
- },
- {
- op: 'add',
- path: '/estimatedGas',
- value: '0x5208',
- },
- ],
- [
- {
- note: '#newUnapprovedTransaction - adding the origin',
- op: 'add',
- path: '/origin',
- timestamp: 1535507561516,
- value: 'MetaMask',
- },
- [],
- ],
- [
- {
- note: 'confTx: user approved transaction',
- op: 'replace',
- path: '/txParams/gasPrice',
- timestamp: 1535664571504,
- value: '0x77359400',
- },
- ],
- [
- {
- note: 'txStateManager: setting status to approved',
- op: 'replace',
- path: '/status',
- timestamp: 1535507564302,
- value: 'approved',
- },
- ],
- [
- {
- note: 'transactions#approveTransaction',
- op: 'add',
- path: '/txParams/nonce',
- timestamp: 1535507564439,
- value: '0xa4',
- },
- {
- op: 'add',
- path: '/nonceDetails',
- value: {
- local: {},
- network: {},
- params: {},
- },
- },
- ],
- [
- {
- note: 'transactions#publishTransaction',
- op: 'replace',
- path: '/status',
- timestamp: 1535507564518,
- value: 'signed',
- },
- {
- op: 'add',
- path: '/rawTx',
- value: '0xf86b81a4843b9aca008252089450a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706872386f26fc10000802aa007b30119fc4fc5954fad727895b7e3ba80a78d197e95703cc603bcf017879151a01c50beda40ffaee541da9c05b9616247074f25f392800e0ad6c7a835d5366edf',
- },
- ],
- [],
- [
- {
- note: 'transactions#setTxHash',
- op: 'add',
- path: '/hash',
- timestamp: 1535507564658,
- value: '0x7acc4987b5c0dfa8d423798a8c561138259de1f98a62e3d52e7e83c0e0dd9fb7',
- },
- ],
- [
- {
- note: 'txStateManager - add submitted time stamp',
- op: 'add',
- path: '/submittedTime',
- timestamp: 1535507564660,
- value: 1535507564660,
- },
- ],
- [
- {
- note: 'txStateManager: setting status to submitted',
- op: 'replace',
- path: '/status',
- timestamp: 1535507564665,
- value: 'submitted',
- },
- ],
- [
- {
- note: 'transactions/pending-tx-tracker#event: tx:block-update',
- op: 'add',
- path: '/firstRetryBlockNumber',
- timestamp: 1535507575476,
- value: '0x3bf624',
- },
- ],
- [
- {
- note: 'txStateManager: setting status to confirmed',
- op: 'replace',
- path: '/status',
- timestamp: 1535507615993,
- value: 'confirmed',
- },
- ],
- ],
- id: 1,
- status: 'confirmed',
- txParams: {
- from: '0x1',
- gas: '0x5208',
- gasPrice: '0x3b9aca00',
- nonce: '0xa4',
- to: '0x2',
- value: '0x2386f26fc10000',
- },
- }
-
- const expectedResult = [
- {
- 'eventKey': 'transactionCreated',
- 'timestamp': 1535507561452,
- 'value': '0x2386f26fc10000',
- },
- {
- 'eventKey': 'transactionUpdatedGas',
- 'timestamp': 1535664571504,
- 'value': '0x77359400',
- },
- {
- 'eventKey': 'transactionSubmitted',
- 'timestamp': 1535507564665,
- 'value': undefined,
- },
- {
- 'eventKey': 'transactionConfirmed',
- 'timestamp': 1535507615993,
- 'value': undefined,
- },
- ]
-
- assert.deepEqual(getActivities(transaction), expectedResult)
- })
-})
diff --git a/ui/app/components/transaction-activity-log/transaction-activity-log.component.js b/ui/app/components/transaction-activity-log/transaction-activity-log.component.js
deleted file mode 100644
index 58d932a0f..000000000
--- a/ui/app/components/transaction-activity-log/transaction-activity-log.component.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import classnames from 'classnames'
-import { getActivities } from './transaction-activity-log.util'
-import Card from '../card'
-import { getEthConversionFromWeiHex, getValueFromWeiHex } from '../../helpers/conversions.util'
-import { formatDate } from '../../util'
-
-export default class TransactionActivityLog extends PureComponent {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- transaction: PropTypes.object,
- className: PropTypes.string,
- conversionRate: PropTypes.number,
- nativeCurrency: PropTypes.string,
- }
-
- state = {
- activities: [],
- }
-
- componentDidMount () {
- this.setActivites()
- }
-
- componentDidUpdate (prevProps) {
- const {
- transaction: { history: prevHistory = [], txReceipt: { status: prevStatus } = {} } = {},
- } = prevProps
- const {
- transaction: { history = [], txReceipt: { status } = {} } = {},
- } = this.props
-
- if (prevHistory.length !== history.length || prevStatus !== status) {
- this.setActivites()
- }
- }
-
- setActivites () {
- const activities = getActivities(this.props.transaction)
- this.setState({ activities })
- }
-
- renderActivity (activity, index) {
- const { conversionRate, nativeCurrency } = this.props
- const { eventKey, value, timestamp } = activity
- const ethValue = index === 0
- ? `${getValueFromWeiHex({
- value,
- fromCurrency: nativeCurrency,
- toCurrency: nativeCurrency,
- conversionRate,
- numberOfDecimals: 6,
- })} ${nativeCurrency}`
- : getEthConversionFromWeiHex({ value, fromCurrency: nativeCurrency, conversionRate })
- const formattedTimestamp = formatDate(timestamp)
- const activityText = this.context.t(eventKey, [ethValue, formattedTimestamp])
-
- return (
- <div
- key={index}
- className="transaction-activity-log__activity"
- >
- <div className="transaction-activity-log__activity-icon" />
- <div
- className="transaction-activity-log__activity-text"
- title={activityText}
- >
- { activityText }
- </div>
- </div>
- )
- }
-
- render () {
- const { t } = this.context
- const { className } = this.props
- const { activities } = this.state
-
- return (
- <div className={classnames('transaction-activity-log', className)}>
- <Card
- title={t('activityLog')}
- className="transaction-activity-log__card"
- >
- <div className="transaction-activity-log__activities-container">
- { activities.map((activity, index) => this.renderActivity(activity, index)) }
- </div>
- </Card>
- </div>
- )
- }
-}
diff --git a/ui/app/components/transaction-activity-log/transaction-activity-log.container.js b/ui/app/components/transaction-activity-log/transaction-activity-log.container.js
deleted file mode 100644
index 622f77df1..000000000
--- a/ui/app/components/transaction-activity-log/transaction-activity-log.container.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { connect } from 'react-redux'
-import TransactionActivityLog from './transaction-activity-log.component'
-import { conversionRateSelector, getNativeCurrency } from '../../selectors'
-
-const mapStateToProps = state => {
- return {
- conversionRate: conversionRateSelector(state),
- nativeCurrency: getNativeCurrency(state),
- }
-}
-
-export default connect(mapStateToProps)(TransactionActivityLog)
diff --git a/ui/app/components/transaction-activity-log/transaction-activity-log.util.js b/ui/app/components/transaction-activity-log/transaction-activity-log.util.js
deleted file mode 100644
index 16597ae1a..000000000
--- a/ui/app/components/transaction-activity-log/transaction-activity-log.util.js
+++ /dev/null
@@ -1,93 +0,0 @@
-// path constants
-const STATUS_PATH = '/status'
-const GAS_PRICE_PATH = '/txParams/gasPrice'
-
-// status constants
-const UNAPPROVED_STATUS = 'unapproved'
-const SUBMITTED_STATUS = 'submitted'
-const CONFIRMED_STATUS = 'confirmed'
-const DROPPED_STATUS = 'dropped'
-
-// op constants
-const REPLACE_OP = 'replace'
-
-// event constants
-const TRANSACTION_CREATED_EVENT = 'transactionCreated'
-const TRANSACTION_UPDATED_GAS_EVENT = 'transactionUpdatedGas'
-const TRANSACTION_SUBMITTED_EVENT = 'transactionSubmitted'
-const TRANSACTION_CONFIRMED_EVENT = 'transactionConfirmed'
-const TRANSACTION_DROPPED_EVENT = 'transactionDropped'
-const TRANSACTION_UPDATED_EVENT = 'transactionUpdated'
-const TRANSACTION_ERRORED_EVENT = 'transactionErrored'
-
-const eventPathsHash = {
- [STATUS_PATH]: true,
- [GAS_PRICE_PATH]: true,
-}
-
-const statusHash = {
- [SUBMITTED_STATUS]: TRANSACTION_SUBMITTED_EVENT,
- [CONFIRMED_STATUS]: TRANSACTION_CONFIRMED_EVENT,
- [DROPPED_STATUS]: TRANSACTION_DROPPED_EVENT,
-}
-
-function eventCreator (eventKey, timestamp, value) {
- return {
- eventKey,
- timestamp,
- value,
- }
-}
-
-export function getActivities (transaction) {
- const { history = [], txReceipt: { status } = {} } = transaction
-
- const historyActivities = history.reduce((acc, base) => {
- // First history item should be transaction creation
- if (!Array.isArray(base) && base.status === UNAPPROVED_STATUS && base.txParams) {
- const { time, txParams: { value } = {} } = base
- return acc.concat(eventCreator(TRANSACTION_CREATED_EVENT, time, value))
- // An entry in the history may be an array of more sub-entries.
- } else if (Array.isArray(base)) {
- const events = []
-
- base.forEach(entry => {
- const { op, path, value, timestamp: entryTimestamp } = entry
- // Not all sub-entries in a history entry have a timestamp. If the sub-entry does not have a
- // timestamp, the first sub-entry in a history entry should.
- const timestamp = entryTimestamp || base[0] && base[0].timestamp
-
- if (path in eventPathsHash && op === REPLACE_OP) {
- switch (path) {
- case STATUS_PATH: {
- if (value in statusHash) {
- events.push(eventCreator(statusHash[value], timestamp))
- }
-
- break
- }
-
- case GAS_PRICE_PATH: {
- events.push(eventCreator(TRANSACTION_UPDATED_GAS_EVENT, timestamp, value))
- break
- }
-
- default: {
- events.push(eventCreator(TRANSACTION_UPDATED_EVENT, timestamp))
- }
- }
- }
- })
-
- return acc.concat(events)
- }
-
- return acc
- }, [])
-
- // If txReceipt.status is '0x0', that means that an on-chain error occured for the transaction,
- // so we add an error entry to the Activity Log.
- return status === '0x0'
- ? historyActivities.concat(eventCreator(TRANSACTION_ERRORED_EVENT))
- : historyActivities
-}
diff --git a/ui/app/components/transaction-breakdown/transaction-breakdown.component.js b/ui/app/components/transaction-breakdown/transaction-breakdown.component.js
deleted file mode 100644
index 3a7647873..000000000
--- a/ui/app/components/transaction-breakdown/transaction-breakdown.component.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import classnames from 'classnames'
-import TransactionBreakdownRow from './transaction-breakdown-row'
-import Card from '../card'
-import CurrencyDisplay from '../currency-display'
-import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
-import HexToDecimal from '../hex-to-decimal'
-import { GWEI, PRIMARY, SECONDARY } from '../../constants/common'
-import { getHexGasTotal } from '../../helpers/confirm-transaction/util'
-import { sumHexes } from '../../helpers/transactions.util'
-
-export default class TransactionBreakdown extends PureComponent {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- transaction: PropTypes.object,
- className: PropTypes.string,
- nativeCurrency: PropTypes.string.isRequired,
- }
-
- static defaultProps = {
- transaction: {},
- }
-
- render () {
- const { t } = this.context
- const { transaction, className, nativeCurrency } = this.props
- const { txParams: { gas, gasPrice, value } = {}, txReceipt: { gasUsed } = {} } = transaction
-
- const gasLimit = typeof gasUsed === 'string' ? gasUsed : gas
-
- const hexGasTotal = getHexGasTotal({ gasLimit, gasPrice })
- const totalInHex = sumHexes(hexGasTotal, value)
-
- return (
- <div className={classnames('transaction-breakdown', className)}>
- <Card
- title={t('transaction')}
- className="transaction-breakdown__card"
- >
- <TransactionBreakdownRow title={t('amount')}>
- <UserPreferencedCurrencyDisplay
- className="transaction-breakdown__value"
- type={PRIMARY}
- value={value}
- />
- </TransactionBreakdownRow>
- <TransactionBreakdownRow
- title={`${t('gasLimit')} (${t('units')})`}
- className="transaction-breakdown__row-title"
- >
- <HexToDecimal
- className="transaction-breakdown__value"
- value={gas}
- />
- </TransactionBreakdownRow>
- {
- typeof gasUsed === 'string' && (
- <TransactionBreakdownRow
- title={`${t('gasUsed')} (${t('units')})`}
- className="transaction-breakdown__row-title"
- >
- <HexToDecimal
- className="transaction-breakdown__value"
- value={gasUsed}
- />
- </TransactionBreakdownRow>
- )
- }
- <TransactionBreakdownRow title={t('gasPrice')}>
- <CurrencyDisplay
- className="transaction-breakdown__value"
- currency={nativeCurrency}
- denomination={GWEI}
- value={gasPrice}
- hideLabel
- />
- </TransactionBreakdownRow>
- <TransactionBreakdownRow title={t('total')}>
- <div>
- <UserPreferencedCurrencyDisplay
- className="transaction-breakdown__value transaction-breakdown__value--eth-total"
- type={PRIMARY}
- value={totalInHex}
- />
- <UserPreferencedCurrencyDisplay
- className="transaction-breakdown__value"
- type={SECONDARY}
- value={totalInHex}
- />
- </div>
- </TransactionBreakdownRow>
- </Card>
- </div>
- )
- }
-}
diff --git a/ui/app/components/transaction-breakdown/transaction-breakdown.container.js b/ui/app/components/transaction-breakdown/transaction-breakdown.container.js
deleted file mode 100644
index ed2708e03..000000000
--- a/ui/app/components/transaction-breakdown/transaction-breakdown.container.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { connect } from 'react-redux'
-import TransactionBreakdown from './transaction-breakdown.component'
-import { getNativeCurrency } from '../../selectors'
-
-const mapStateToProps = (state) => {
- return {
- nativeCurrency: getNativeCurrency(state),
- }
-}
-
-export default connect(mapStateToProps)(TransactionBreakdown)
diff --git a/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js b/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js
deleted file mode 100644
index a4f28fd63..000000000
--- a/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js
+++ /dev/null
@@ -1,111 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import SenderToRecipient from '../sender-to-recipient'
-import { CARDS_VARIANT } from '../sender-to-recipient/sender-to-recipient.constants'
-import TransactionActivityLog from '../transaction-activity-log'
-import TransactionBreakdown from '../transaction-breakdown'
-import Button from '../button'
-import Tooltip from '../tooltip'
-import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
-
-export default class TransactionListItemDetails extends PureComponent {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- onCancel: PropTypes.func,
- onRetry: PropTypes.func,
- showCancel: PropTypes.bool,
- showRetry: PropTypes.bool,
- transaction: PropTypes.object,
- }
-
- handleEtherscanClick = () => {
- const { hash, metamaskNetworkId } = this.props.transaction
-
- const prefix = prefixForNetwork(metamaskNetworkId)
- const etherscanUrl = `https://${prefix}etherscan.io/tx/${hash}`
- global.platform.openWindow({ url: etherscanUrl })
- this.setState({ showTransactionDetails: true })
- }
-
- handleCancel = event => {
- const { onCancel } = this.props
-
- event.stopPropagation()
- onCancel()
- }
-
- handleRetry = event => {
- const { onRetry } = this.props
-
- event.stopPropagation()
- onRetry()
- }
-
- render () {
- const { t } = this.context
- const { transaction, showCancel, showRetry } = this.props
- const { txParams: { to, from } = {} } = transaction
-
- return (
- <div className="transaction-list-item-details">
- <div className="transaction-list-item-details__header">
- <div>Details</div>
- <div className="transaction-list-item-details__header-buttons">
- {
- showRetry && (
- <Button
- type="raised"
- onClick={this.handleRetry}
- className="transaction-list-item-details__header-button"
- >
- { t('speedUp') }
- </Button>
- )
- }
- {
- showCancel && (
- <Button
- type="raised"
- onClick={this.handleCancel}
- className="transaction-list-item-details__header-button"
- >
- { t('cancel') }
- </Button>
- )
- }
- <Tooltip title={t('viewOnEtherscan')}>
- <Button
- type="raised"
- onClick={this.handleEtherscanClick}
- className="transaction-list-item-details__header-button"
- >
- <img src="/images/arrow-popout.svg" />
- </Button>
- </Tooltip>
- </div>
- </div>
- <div className="transaction-list-item-details__sender-to-recipient-container">
- <SenderToRecipient
- variant={CARDS_VARIANT}
- addressOnly
- recipientAddress={to}
- senderAddress={from}
- />
- </div>
- <div className="transaction-list-item-details__cards-container">
- <TransactionBreakdown
- transaction={transaction}
- className="transaction-list-item-details__transaction-breakdown"
- />
- <TransactionActivityLog
- transaction={transaction}
- className="transaction-list-item-details__transaction-activity-log"
- />
- </div>
- </div>
- )
- }
-}
diff --git a/ui/app/components/transaction-list-item/transaction-list-item.container.js b/ui/app/components/transaction-list-item/transaction-list-item.container.js
deleted file mode 100644
index 62ed7a73f..000000000
--- a/ui/app/components/transaction-list-item/transaction-list-item.container.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import { connect } from 'react-redux'
-import { withRouter } from 'react-router-dom'
-import { compose } from 'recompose'
-import withMethodData from '../../higher-order-components/with-method-data'
-import TransactionListItem from './transaction-list-item.component'
-import { setSelectedToken, retryTransaction, showModal } from '../../actions'
-import { hexToDecimal } from '../../helpers/conversions.util'
-import { getTokenData } from '../../helpers/transactions.util'
-import { formatDate } from '../../util'
-
-const mapStateToProps = (state, ownProps) => {
- const { transaction: { txParams: { value, nonce, data } = {}, time } = {} } = ownProps
-
- const tokenData = data && getTokenData(data)
- const nonceAndDate = nonce ? `#${hexToDecimal(nonce)} - ${formatDate(time)}` : formatDate(time)
-
- return {
- value,
- nonceAndDate,
- tokenData,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- setSelectedToken: tokenAddress => dispatch(setSelectedToken(tokenAddress)),
- retryTransaction: transactionId => dispatch(retryTransaction(transactionId)),
- showCancelModal: (transactionId, originalGasPrice) => {
- return dispatch(showModal({ name: 'CANCEL_TRANSACTION', transactionId, originalGasPrice }))
- },
- }
-}
-
-export default compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps),
- withMethodData,
-)(TransactionListItem)
diff --git a/ui/app/components/transaction-view-balance/transaction-view-balance.component.js b/ui/app/components/transaction-view-balance/transaction-view-balance.component.js
deleted file mode 100644
index 402b0f486..000000000
--- a/ui/app/components/transaction-view-balance/transaction-view-balance.component.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import Button from '../button'
-import Identicon from '../identicon'
-import TokenBalance from '../token-balance'
-import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
-import { SEND_ROUTE } from '../../routes'
-import { PRIMARY, SECONDARY } from '../../constants/common'
-
-export default class TransactionViewBalance extends PureComponent {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- showDepositModal: PropTypes.func,
- selectedToken: PropTypes.object,
- history: PropTypes.object,
- network: PropTypes.string,
- balance: PropTypes.string,
- assetImage: PropTypes.string,
- }
-
- renderBalance () {
- const { selectedToken, balance } = this.props
-
- return selectedToken
- ? (
- <TokenBalance
- token={selectedToken}
- withSymbol
- className="transaction-view-balance__token-balance"
- />
- ) : (
- <div className="transaction-view-balance__balance">
- <UserPreferencedCurrencyDisplay
- className="transaction-view-balance__primary-balance"
- value={balance}
- type={PRIMARY}
- ethNumberOfDecimals={4}
- />
- <UserPreferencedCurrencyDisplay
- className="transaction-view-balance__secondary-balance"
- value={balance}
- type={SECONDARY}
- ethNumberOfDecimals={4}
- />
- </div>
- )
- }
-
- renderButtons () {
- const { t } = this.context
- const { selectedToken, showDepositModal, history } = this.props
-
- return (
- <div className="transaction-view-balance__buttons">
- {
- !selectedToken && (
- <Button
- type="primary"
- className="transaction-view-balance__button"
- onClick={() => showDepositModal()}
- >
- { t('deposit') }
- </Button>
- )
- }
- <Button
- type="primary"
- className="transaction-view-balance__button"
- onClick={() => history.push(SEND_ROUTE)}
- >
- { t('send') }
- </Button>
- </div>
- )
- }
-
- render () {
- const { network, selectedToken, assetImage } = this.props
-
- return (
- <div className="transaction-view-balance">
- <div className="transaction-view-balance__balance-container">
- <Identicon
- diameter={50}
- address={selectedToken && selectedToken.address}
- network={network}
- image={assetImage}
- />
- { this.renderBalance() }
- </div>
- { this.renderButtons() }
- </div>
- )
- }
-}
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
new file mode 100644
index 000000000..8abe1ab18
--- /dev/null
+++ b/ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js
@@ -0,0 +1,84 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import AccountListItem from '../../app/send/account-list-item/account-list-item.component'
+
+export default class AccountDropdownMini extends PureComponent {
+ static propTypes = {
+ accounts: PropTypes.array.isRequired,
+ closeDropdown: PropTypes.func,
+ disabled: PropTypes.bool,
+ dropdownOpen: PropTypes.bool,
+ onSelect: PropTypes.func,
+ openDropdown: PropTypes.func,
+ selectedAccount: PropTypes.object.isRequired,
+ }
+
+ static defaultProps = {
+ closeDropdown: () => {},
+ disabled: false,
+ dropdownOpen: false,
+ onSelect: () => {},
+ openDropdown: () => {},
+ }
+
+ getListItemIcon (currentAccount, selectedAccount) {
+ return currentAccount.address === selectedAccount.address && (
+ <i
+ className="fa fa-check fa-lg"
+ style={{ color: '#02c9b1' }}
+ />
+ )
+ }
+
+ renderDropdown () {
+ const { accounts, selectedAccount, closeDropdown, onSelect } = this.props
+
+ return (
+ <div>
+ <div
+ className="account-dropdown-mini__close-area"
+ onClick={closeDropdown}
+ />
+ <div className="account-dropdown-mini__list">
+ {
+ accounts.map(account => (
+ <AccountListItem
+ key={account.address}
+ account={account}
+ displayBalance={false}
+ displayAddress={false}
+ handleClick={() => {
+ onSelect(account)
+ closeDropdown()
+ }}
+ icon={this.getListItemIcon(account, selectedAccount)}
+ />
+ ))
+ }
+ </div>
+ </div>
+ )
+ }
+
+ render () {
+ const { disabled, selectedAccount, openDropdown, dropdownOpen } = this.props
+
+ return (
+ <div className="account-dropdown-mini">
+ <AccountListItem
+ account={selectedAccount}
+ handleClick={() => !disabled && openDropdown()}
+ displayBalance={false}
+ displayAddress={false}
+ icon={
+ !disabled && <i
+ className="fa fa-caret-down fa-lg"
+ style={{ color: '#dedede' }}
+ />
+ }
+ />
+ { !disabled && dropdownOpen && this.renderDropdown() }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/ui/account-dropdown-mini/index.js b/ui/app/components/ui/account-dropdown-mini/index.js
new file mode 100644
index 000000000..cb0839e72
--- /dev/null
+++ b/ui/app/components/ui/account-dropdown-mini/index.js
@@ -0,0 +1 @@
+export { default } from './account-dropdown-mini.component'
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
new file mode 100644
index 000000000..bc74ceb3c
--- /dev/null
+++ b/ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js
@@ -0,0 +1,107 @@
+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'
+
+describe('AccountDropdownMini', () => {
+ it('should render an account with an icon', () => {
+ const accounts = [
+ {
+ address: '0x1',
+ name: 'account1',
+ balance: '0x1',
+ },
+ {
+ address: '0x2',
+ name: 'account2',
+ balance: '0x2',
+ },
+ {
+ address: '0x3',
+ name: 'account3',
+ balance: '0x3',
+ },
+ ]
+
+ const wrapper = shallow(
+ <AccountDropdownMini
+ selectedAccount={{ address: '0x1', name: 'account1', balance: '0x1' }}
+ accounts={accounts}
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find(AccountListItem).length, 1)
+ const accountListItemProps = wrapper.find(AccountListItem).at(0).props()
+ assert.equal(accountListItemProps.account.address, '0x1')
+ const iconProps = accountListItemProps.icon.props
+ assert.equal(iconProps.className, 'fa fa-caret-down fa-lg')
+ })
+
+ it('should render a list of accounts', () => {
+ const accounts = [
+ {
+ address: '0x1',
+ name: 'account1',
+ balance: '0x1',
+ },
+ {
+ address: '0x2',
+ name: 'account2',
+ balance: '0x2',
+ },
+ {
+ address: '0x3',
+ name: 'account3',
+ balance: '0x3',
+ },
+ ]
+
+ const wrapper = shallow(
+ <AccountDropdownMini
+ selectedAccount={{ address: '0x1', name: 'account1', balance: '0x1' }}
+ accounts={accounts}
+ dropdownOpen={true}
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find(AccountListItem).length, 4)
+ })
+
+ it('should render a single account when disabled', () => {
+ const accounts = [
+ {
+ address: '0x1',
+ name: 'account1',
+ balance: '0x1',
+ },
+ {
+ address: '0x2',
+ name: 'account2',
+ balance: '0x2',
+ },
+ {
+ address: '0x3',
+ name: 'account3',
+ balance: '0x3',
+ },
+ ]
+
+ const wrapper = shallow(
+ <AccountDropdownMini
+ selectedAccount={{ address: '0x1', name: 'account1', balance: '0x1' }}
+ accounts={accounts}
+ dropdownOpen={false}
+ disabled={true}
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find(AccountListItem).length, 1)
+ const accountListItemProps = wrapper.find(AccountListItem).at(0).props()
+ assert.equal(accountListItemProps.account.address, '0x1')
+ assert.equal(accountListItemProps.icon, false)
+ })
+})
diff --git a/ui/app/components/alert/index.js b/ui/app/components/ui/alert/index.js
index 5620d847a..5620d847a 100644
--- a/ui/app/components/alert/index.js
+++ b/ui/app/components/ui/alert/index.js
diff --git a/ui/app/components/ui/balance/balance.component.js b/ui/app/components/ui/balance/balance.component.js
new file mode 100644
index 000000000..9a6f71ce5
--- /dev/null
+++ b/ui/app/components/ui/balance/balance.component.js
@@ -0,0 +1,92 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import TokenBalance from '../token-balance'
+import Identicon from '../identicon'
+import UserPreferencedCurrencyDisplay from '../../app/user-preferenced-currency-display'
+import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'
+import { formatBalance } from '../../../helpers/utils/util'
+
+export default class Balance extends PureComponent {
+ static propTypes = {
+ account: PropTypes.object,
+ assetImages: PropTypes.object,
+ nativeCurrency: PropTypes.string,
+ needsParse: PropTypes.bool,
+ network: PropTypes.string,
+ showFiat: PropTypes.bool,
+ token: PropTypes.object,
+ }
+
+ static defaultProps = {
+ needsParse: true,
+ showFiat: true,
+ }
+
+ renderBalance () {
+ const { account, nativeCurrency, needsParse, showFiat } = this.props
+ const balanceValue = account && account.balance
+ const formattedBalance = balanceValue
+ ? formatBalance(balanceValue, 6, needsParse, nativeCurrency)
+ : '...'
+
+ if (formattedBalance === 'None' || formattedBalance === '...') {
+ return (
+ <div className="flex-column balance-display">
+ <div className="token-amount">
+ { formattedBalance }
+ </div>
+ </div>
+ )
+ }
+
+ return (
+ <div className="flex-column balance-display">
+ <UserPreferencedCurrencyDisplay
+ className="token-amount"
+ value={balanceValue}
+ type={PRIMARY}
+ ethNumberOfDecimals={4}
+ />
+ {
+ showFiat && (
+ <UserPreferencedCurrencyDisplay
+ value={balanceValue}
+ type={SECONDARY}
+ ethNumberOfDecimals={4}
+ />
+ )
+ }
+ </div>
+ )
+ }
+
+ renderTokenBalance () {
+ const { token } = this.props
+
+ return (
+ <div className="flex-column balance-display">
+ <div className="token-amount">
+ <TokenBalance token={token} />
+ </div>
+ </div>
+ )
+ }
+
+ render () {
+ const { token, network, assetImages } = this.props
+ const address = token && token.address
+ const image = assetImages && address ? assetImages[token.address] : undefined
+
+ return (
+ <div className="balance-container">
+ <Identicon
+ diameter={50}
+ address={address}
+ network={network}
+ image={image}
+ />
+ { token ? this.renderTokenBalance() : this.renderBalance() }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/ui/balance/balance.container.js b/ui/app/components/ui/balance/balance.container.js
new file mode 100644
index 000000000..2ad5c5ad8
--- /dev/null
+++ b/ui/app/components/ui/balance/balance.container.js
@@ -0,0 +1,32 @@
+import { connect } from 'react-redux'
+import Balance from './balance.component'
+import {
+ getNativeCurrency,
+ getAssetImages,
+ conversionRateSelector,
+ getCurrentCurrency,
+ getMetaMaskAccounts,
+ getIsMainnet,
+ preferencesSelector,
+} from '../../../selectors/selectors'
+
+const mapStateToProps = state => {
+ const { showFiatInTestnets } = preferencesSelector(state)
+ const isMainnet = getIsMainnet(state)
+ const accounts = getMetaMaskAccounts(state)
+ const network = state.metamask.network
+ const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
+ const account = accounts[selectedAddress]
+
+ return {
+ account,
+ network,
+ nativeCurrency: getNativeCurrency(state),
+ conversionRate: conversionRateSelector(state),
+ currentCurrency: getCurrentCurrency(state),
+ assetImages: getAssetImages(state),
+ showFiat: (isMainnet || !!showFiatInTestnets),
+ }
+}
+
+export default connect(mapStateToProps)(Balance)
diff --git a/ui/app/components/ui/balance/index.js b/ui/app/components/ui/balance/index.js
new file mode 100644
index 000000000..f8fb9ea19
--- /dev/null
+++ b/ui/app/components/ui/balance/index.js
@@ -0,0 +1 @@
+export { default } from './balance.container'
diff --git a/ui/app/components/ui/breadcrumbs/breadcrumbs.component.js b/ui/app/components/ui/breadcrumbs/breadcrumbs.component.js
new file mode 100644
index 000000000..6644836db
--- /dev/null
+++ b/ui/app/components/ui/breadcrumbs/breadcrumbs.component.js
@@ -0,0 +1,29 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+
+export default class Breadcrumbs extends PureComponent {
+ static propTypes = {
+ className: PropTypes.string,
+ currentIndex: PropTypes.number,
+ total: PropTypes.number,
+ }
+
+ render () {
+ const { className, currentIndex, total } = this.props
+
+ return (
+ <div className={classnames('breadcrumbs', className)}>
+ {
+ Array(total).fill().map((_, i) => (
+ <div
+ key={i}
+ className="breadcrumb"
+ style={{backgroundColor: i === currentIndex ? '#D8D8D8' : '#FFFFFF'}}
+ />
+ ))
+ }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/ui/breadcrumbs/index.js b/ui/app/components/ui/breadcrumbs/index.js
new file mode 100644
index 000000000..07a11574f
--- /dev/null
+++ b/ui/app/components/ui/breadcrumbs/index.js
@@ -0,0 +1 @@
+export { default } from './breadcrumbs.component'
diff --git a/ui/app/components/ui/breadcrumbs/index.scss b/ui/app/components/ui/breadcrumbs/index.scss
new file mode 100644
index 000000000..e23aa7970
--- /dev/null
+++ b/ui/app/components/ui/breadcrumbs/index.scss
@@ -0,0 +1,15 @@
+.breadcrumbs {
+ display: flex;
+ flex-flow: row nowrap;
+}
+
+.breadcrumb {
+ height: 10px;
+ width: 10px;
+ border: 1px solid #979797;
+ border-radius: 50%;
+}
+
+.breadcrumb + .breadcrumb {
+ margin-left: 10px;
+}
diff --git a/ui/app/components/ui/breadcrumbs/tests/breadcrumbs.component.test.js b/ui/app/components/ui/breadcrumbs/tests/breadcrumbs.component.test.js
new file mode 100644
index 000000000..5013c5b60
--- /dev/null
+++ b/ui/app/components/ui/breadcrumbs/tests/breadcrumbs.component.test.js
@@ -0,0 +1,22 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import Breadcrumbs from '../breadcrumbs.component'
+
+describe('Breadcrumbs Component', () => {
+ it('should render with the correct colors', () => {
+ const wrapper = shallow(
+ <Breadcrumbs
+ currentIndex={1}
+ total={3}
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find('.breadcrumbs').length, 1)
+ assert.equal(wrapper.find('.breadcrumb').length, 3)
+ assert.equal(wrapper.find('.breadcrumb').at(0).props().style['backgroundColor'], '#FFFFFF')
+ assert.equal(wrapper.find('.breadcrumb').at(1).props().style['backgroundColor'], '#D8D8D8')
+ assert.equal(wrapper.find('.breadcrumb').at(2).props().style['backgroundColor'], '#FFFFFF')
+ })
+})
diff --git a/ui/app/components/button-group/button-group.component.js b/ui/app/components/ui/button-group/button-group.component.js
index f99f710ce..17a281030 100644
--- a/ui/app/components/button-group/button-group.component.js
+++ b/ui/app/components/ui/button-group/button-group.component.js
@@ -5,18 +5,30 @@ import classnames from 'classnames'
export default class ButtonGroup extends PureComponent {
static propTypes = {
defaultActiveButtonIndex: PropTypes.number,
+ noButtonActiveByDefault: PropTypes.bool,
disabled: PropTypes.bool,
children: PropTypes.array,
className: PropTypes.string,
style: PropTypes.object,
+ newActiveButtonIndex: PropTypes.number,
}
static defaultProps = {
className: 'button-group',
+ defaultActiveButtonIndex: 0,
}
state = {
- activeButtonIndex: this.props.defaultActiveButtonIndex || 0,
+ activeButtonIndex: this.props.noButtonActiveByDefault
+ ? null
+ : this.props.defaultActiveButtonIndex,
+ }
+
+ componentDidUpdate (_, prevState) {
+ // Provides an API for dynamically updating the activeButtonIndex
+ if (typeof this.props.newActiveButtonIndex === 'number' && prevState.activeButtonIndex !== this.props.newActiveButtonIndex) {
+ this.setState({ activeButtonIndex: this.props.newActiveButtonIndex })
+ }
}
handleButtonClick (activeButtonIndex) {
diff --git a/ui/app/components/button-group/button-group.stories.js b/ui/app/components/ui/button-group/button-group.stories.js
index 14e1a7e49..c58c628b3 100644
--- a/ui/app/components/button-group/button-group.stories.js
+++ b/ui/app/components/ui/button-group/button-group.stories.js
@@ -1,7 +1,7 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
-import ButtonGroup from './'
+import ButtonGroup from '.'
import Button from '../button'
import { text, boolean } from '@storybook/addon-knobs/react'
diff --git a/ui/app/components/button-group/index.js b/ui/app/components/ui/button-group/index.js
index df470bd57..df470bd57 100644
--- a/ui/app/components/button-group/index.js
+++ b/ui/app/components/ui/button-group/index.js
diff --git a/ui/app/components/button-group/index.scss b/ui/app/components/ui/button-group/index.scss
index 29713c75b..29713c75b 100644
--- a/ui/app/components/button-group/index.scss
+++ b/ui/app/components/ui/button-group/index.scss
diff --git a/ui/app/components/button-group/tests/button-group-component.test.js b/ui/app/components/ui/button-group/tests/button-group-component.test.js
index f07bb97c8..0bece90d6 100644
--- a/ui/app/components/button-group/tests/button-group-component.test.js
+++ b/ui/app/components/ui/button-group/tests/button-group-component.test.js
@@ -35,6 +35,20 @@ describe('ButtonGroup Component', function () {
ButtonGroup.prototype.renderButtons.resetHistory()
})
+ describe('componentDidUpdate', () => {
+ it('should set the activeButtonIndex to the updated newActiveButtonIndex', () => {
+ assert.equal(wrapper.state('activeButtonIndex'), 1)
+ wrapper.setProps({ newActiveButtonIndex: 2 })
+ assert.equal(wrapper.state('activeButtonIndex'), 2)
+ })
+
+ it('should not set the activeButtonIndex to an updated newActiveButtonIndex that is not a number', () => {
+ assert.equal(wrapper.state('activeButtonIndex'), 1)
+ wrapper.setProps({ newActiveButtonIndex: null })
+ assert.equal(wrapper.state('activeButtonIndex'), 1)
+ })
+ })
+
describe('handleButtonClick', () => {
it('should set the activeButtonIndex', () => {
assert.equal(wrapper.state('activeButtonIndex'), 1)
diff --git a/ui/app/components/button/button.component.js b/ui/app/components/ui/button/button.component.js
index 4a06333e7..5d19219b4 100644
--- a/ui/app/components/button/button.component.js
+++ b/ui/app/components/ui/button/button.component.js
@@ -8,6 +8,7 @@ const CLASSNAME_SECONDARY = 'btn-secondary'
const CLASSNAME_CONFIRM = 'btn-confirm'
const CLASSNAME_RAISED = 'btn-raised'
const CLASSNAME_LARGE = 'btn--large'
+const CLASSNAME_FIRST_TIME = 'btn--first-time'
const typeHash = {
default: CLASSNAME_DEFAULT,
@@ -15,6 +16,7 @@ const typeHash = {
secondary: CLASSNAME_SECONDARY,
confirm: CLASSNAME_CONFIRM,
raised: CLASSNAME_RAISED,
+ 'first-time': CLASSNAME_FIRST_TIME,
}
export default class Button extends Component {
@@ -22,7 +24,11 @@ export default class Button extends Component {
type: PropTypes.string,
large: PropTypes.bool,
className: PropTypes.string,
- children: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
+ children: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.array,
+ PropTypes.element,
+ ]),
}
render () {
diff --git a/ui/app/components/button/button.stories.js b/ui/app/components/ui/button/button.stories.js
index dec084a25..667824a47 100644
--- a/ui/app/components/button/button.stories.js
+++ b/ui/app/components/ui/button/button.stories.js
@@ -1,7 +1,7 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
-import Button from './'
+import Button from '.'
import { text } from '@storybook/addon-knobs/react'
storiesOf('Button', module)
diff --git a/ui/app/components/button/index.js b/ui/app/components/ui/button/index.js
index 33ae95ae2..33ae95ae2 100644
--- a/ui/app/components/button/index.js
+++ b/ui/app/components/ui/button/index.js
diff --git a/ui/app/components/card/card.component.js b/ui/app/components/ui/card/card.component.js
index bb7241da1..bb7241da1 100644
--- a/ui/app/components/card/card.component.js
+++ b/ui/app/components/ui/card/card.component.js
diff --git a/ui/app/components/card/index.js b/ui/app/components/ui/card/index.js
index c3ca6e3f4..c3ca6e3f4 100644
--- a/ui/app/components/card/index.js
+++ b/ui/app/components/ui/card/index.js
diff --git a/ui/app/components/card/index.scss b/ui/app/components/ui/card/index.scss
index bde54a15e..bde54a15e 100644
--- a/ui/app/components/card/index.scss
+++ b/ui/app/components/ui/card/index.scss
diff --git a/ui/app/components/card/tests/card.component.test.js b/ui/app/components/ui/card/tests/card.component.test.js
index cea05033f..cea05033f 100644
--- a/ui/app/components/card/tests/card.component.test.js
+++ b/ui/app/components/ui/card/tests/card.component.test.js
diff --git a/ui/app/components/copyButton.js b/ui/app/components/ui/copyButton.js
index a60d33523..a60d33523 100644
--- a/ui/app/components/copyButton.js
+++ b/ui/app/components/ui/copyButton.js
diff --git a/ui/app/components/currency-display/currency-display.component.js b/ui/app/components/ui/currency-display/currency-display.component.js
index 2d7413b57..04dd89892 100644
--- a/ui/app/components/currency-display/currency-display.component.js
+++ b/ui/app/components/ui/currency-display/currency-display.component.js
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-import { GWEI } from '../../constants/common'
+import { GWEI } from '../../../helpers/constants/common'
export default class CurrencyDisplay extends PureComponent {
static propTypes = {
@@ -17,10 +17,11 @@ export default class CurrencyDisplay extends PureComponent {
value: PropTypes.string,
numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
hideLabel: PropTypes.bool,
+ hideTitle: PropTypes.bool,
}
render () {
- const { className, displayValue, prefix, prefixComponent, style, suffix } = this.props
+ const { className, displayValue, prefix, prefixComponent, style, suffix, hideTitle } = this.props
const text = `${prefix || ''}${displayValue}`
const title = `${text} ${suffix}`
@@ -28,9 +29,9 @@ export default class CurrencyDisplay extends PureComponent {
<div
className={classnames('currency-display-component', className)}
style={style}
- title={title}
+ title={!hideTitle && title || null}
>
- { prefixComponent}
+ { prefixComponent }
<span className="currency-display-component__text">{ text }</span>
{
suffix && (
diff --git a/ui/app/components/currency-display/currency-display.container.js b/ui/app/components/ui/currency-display/currency-display.container.js
index 6ddf07172..093d99c8e 100644
--- a/ui/app/components/currency-display/currency-display.container.js
+++ b/ui/app/components/ui/currency-display/currency-display.container.js
@@ -1,6 +1,6 @@
import { connect } from 'react-redux'
import CurrencyDisplay from './currency-display.component'
-import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util'
+import { getValueFromWeiHex, formatCurrency } from '../../../helpers/utils/confirm-tx.util'
const mapStateToProps = state => {
const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state
@@ -20,15 +20,24 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
currency,
denomination,
hideLabel,
+ displayValue: propsDisplayValue,
+ suffix: propsSuffix,
...restOwnProps
} = ownProps
const toCurrency = currency || currentCurrency
- const convertedValue = getValueFromWeiHex({
- value, fromCurrency: nativeCurrency, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
- })
- const displayValue = formatCurrency(convertedValue, toCurrency)
- const suffix = hideLabel ? undefined : toCurrency.toUpperCase()
+
+ const displayValue = propsDisplayValue || formatCurrency(
+ getValueFromWeiHex({
+ value,
+ fromCurrency: nativeCurrency,
+ toCurrency, conversionRate,
+ numberOfDecimals,
+ toDenomination: denomination,
+ }),
+ toCurrency
+ )
+ const suffix = propsSuffix || (hideLabel ? undefined : toCurrency.toUpperCase())
return {
...restStateProps,
diff --git a/ui/app/components/currency-display/index.js b/ui/app/components/ui/currency-display/index.js
index 38f08765f..38f08765f 100644
--- a/ui/app/components/currency-display/index.js
+++ b/ui/app/components/ui/currency-display/index.js
diff --git a/ui/app/components/currency-display/index.scss b/ui/app/components/ui/currency-display/index.scss
index 313c932b8..313c932b8 100644
--- a/ui/app/components/currency-display/index.scss
+++ b/ui/app/components/ui/currency-display/index.scss
diff --git a/ui/app/components/currency-display/tests/currency-display.component.test.js b/ui/app/components/ui/currency-display/tests/currency-display.component.test.js
index d9ef052f1..d9ef052f1 100644
--- a/ui/app/components/currency-display/tests/currency-display.component.test.js
+++ b/ui/app/components/ui/currency-display/tests/currency-display.component.test.js
diff --git a/ui/app/components/currency-display/tests/currency-display.container.test.js b/ui/app/components/ui/currency-display/tests/currency-display.container.test.js
index 0c886af50..9888c366e 100644
--- a/ui/app/components/currency-display/tests/currency-display.container.test.js
+++ b/ui/app/components/ui/currency-display/tests/currency-display.container.test.js
@@ -131,7 +131,7 @@ describe('CurrencyDisplay container', () => {
},
result: {
nativeCurrency: 'ETH',
- displayValue: '1e-9',
+ displayValue: '0.000000001',
suffix: undefined,
},
},
diff --git a/ui/app/components/currency-input/currency-input.component.js b/ui/app/components/ui/currency-input/currency-input.component.js
index 0761a75c5..b5be0972b 100644
--- a/ui/app/components/currency-input/currency-input.component.js
+++ b/ui/app/components/ui/currency-input/currency-input.component.js
@@ -2,8 +2,8 @@ import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import UnitInput from '../unit-input'
import CurrencyDisplay from '../currency-display'
-import { getValueFromWeiHex, getWeiHexFromDecimalValue } from '../../helpers/conversions.util'
-import { ETH } from '../../constants/common'
+import { getValueFromWeiHex, getWeiHexFromDecimalValue } from '../../../helpers/utils/conversions.util'
+import { ETH } from '../../../helpers/constants/common'
/**
* Component that allows user to enter currency values as a number, and props receive a converted
@@ -11,15 +11,21 @@ import { ETH } from '../../constants/common'
* gets converted into a decimal value depending on the currency (ETH or Fiat).
*/
export default class CurrencyInput extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
static propTypes = {
conversionRate: PropTypes.number,
currentCurrency: PropTypes.string,
nativeCurrency: PropTypes.string,
onChange: PropTypes.func,
onBlur: PropTypes.func,
- suffix: PropTypes.string,
useFiat: PropTypes.bool,
+ hideFiat: PropTypes.bool,
value: PropTypes.string,
+ fiatSuffix: PropTypes.string,
+ nativeSuffix: PropTypes.string,
}
constructor (props) {
@@ -31,6 +37,7 @@ export default class CurrencyInput extends PureComponent {
this.state = {
decimalValue,
hexValue,
+ isSwapped: false,
}
}
@@ -46,8 +53,8 @@ export default class CurrencyInput extends PureComponent {
}
getDecimalValue (props) {
- const { value: hexValue, useFiat, currentCurrency, conversionRate } = props
- const decimalValueString = useFiat
+ const { value: hexValue, currentCurrency, conversionRate } = props
+ const decimalValueString = this.shouldUseFiat()
? getValueFromWeiHex({
value: hexValue, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
})
@@ -58,10 +65,28 @@ export default class CurrencyInput extends PureComponent {
return Number(decimalValueString) || 0
}
+ shouldUseFiat = () => {
+ const { useFiat, hideFiat } = this.props
+ const { isSwapped } = this.state || {}
+
+ if (hideFiat) {
+ return false
+ }
+
+ return isSwapped ? !useFiat : useFiat
+ }
+
+ swap = () => {
+ const { isSwapped, decimalValue } = this.state
+ this.setState({isSwapped: !isSwapped}, () => {
+ this.handleChange(decimalValue)
+ })
+ }
+
handleChange = decimalValue => {
- const { useFiat, currentCurrency: fromCurrency, conversionRate, onChange } = this.props
+ const { currentCurrency: fromCurrency, conversionRate, onChange } = this.props
- const hexValue = useFiat
+ const hexValue = this.shouldUseFiat()
? getWeiHexFromDecimalValue({
value: decimalValue, fromCurrency, conversionRate, invertConversionRate: true,
})
@@ -78,11 +103,19 @@ export default class CurrencyInput extends PureComponent {
}
renderConversionComponent () {
- const { useFiat, currentCurrency, nativeCurrency } = this.props
+ const { currentCurrency, nativeCurrency, hideFiat } = this.props
const { hexValue } = this.state
let currency, numberOfDecimals
- if (useFiat) {
+ if (hideFiat) {
+ return (
+ <div className="currency-input__conversion-component">
+ { this.context.t('noConversionRateAvailable') }
+ </div>
+ )
+ }
+
+ if (this.shouldUseFiat()) {
// Display ETH
currency = nativeCurrency || ETH
numberOfDecimals = 6
@@ -103,19 +136,25 @@ export default class CurrencyInput extends PureComponent {
}
render () {
- const { suffix, ...restProps } = this.props
+ const { fiatSuffix, nativeSuffix, ...restProps } = this.props
const { decimalValue } = this.state
return (
- <UnitInput
- {...restProps}
- suffix={suffix}
- onChange={this.handleChange}
- onBlur={this.handleBlur}
- value={decimalValue}
- >
- { this.renderConversionComponent() }
- </UnitInput>
+ <UnitInput
+ {...restProps}
+ suffix={this.shouldUseFiat() ? fiatSuffix : nativeSuffix}
+ onChange={this.handleChange}
+ onBlur={this.handleBlur}
+ value={decimalValue}
+ actionComponent={(
+ <div
+ className="currency-input__swap-component"
+ onClick={this.swap}
+ />
+ )}
+ >
+ { this.renderConversionComponent() }
+ </UnitInput>
)
}
}
diff --git a/ui/app/components/currency-input/currency-input.container.js b/ui/app/components/ui/currency-input/currency-input.container.js
index 1d1ed7b41..b5d7dfe6d 100644
--- a/ui/app/components/currency-input/currency-input.container.js
+++ b/ui/app/components/ui/currency-input/currency-input.container.js
@@ -1,27 +1,30 @@
import { connect } from 'react-redux'
import CurrencyInput from './currency-input.component'
-import { ETH } from '../../constants/common'
+import { ETH } from '../../../helpers/constants/common'
+import {getIsMainnet, preferencesSelector} from '../../../selectors/selectors'
const mapStateToProps = state => {
const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state
+ const { showFiatInTestnets } = preferencesSelector(state)
+ const isMainnet = getIsMainnet(state)
return {
nativeCurrency,
currentCurrency,
conversionRate,
+ hideFiat: (!isMainnet && !showFiatInTestnets),
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { nativeCurrency, currentCurrency } = stateProps
- const { useFiat } = ownProps
- const suffix = useFiat ? currentCurrency.toUpperCase() : nativeCurrency || ETH
return {
...stateProps,
...dispatchProps,
...ownProps,
- suffix,
+ nativeSuffix: nativeCurrency || ETH,
+ fiatSuffix: currentCurrency.toUpperCase(),
}
}
diff --git a/ui/app/components/currency-input/index.js b/ui/app/components/ui/currency-input/index.js
index d8069fb67..d8069fb67 100644
--- a/ui/app/components/currency-input/index.js
+++ b/ui/app/components/ui/currency-input/index.js
diff --git a/ui/app/components/ui/currency-input/index.scss b/ui/app/components/ui/currency-input/index.scss
new file mode 100644
index 000000000..f659f5b35
--- /dev/null
+++ b/ui/app/components/ui/currency-input/index.scss
@@ -0,0 +1,26 @@
+.currency-input {
+ &__conversion-component {
+ font-size: 12px;
+ line-height: 12px;
+ padding-left: 1px;
+ }
+
+ &__swap-component {
+ flex: 0 0 auto;
+ height: 24px;
+ width: 24px;
+ background-image: url("images/icons/swap.svg");
+ background-size: contain;
+ background-repeat: no-repeat;
+ cursor: pointer;
+ opacity: .4;
+
+ &:hover {
+ opacity: .3;
+ }
+
+ &:active {
+ opacity: .5;
+ }
+ }
+}
diff --git a/ui/app/components/currency-input/tests/currency-input.component.test.js b/ui/app/components/ui/currency-input/tests/currency-input.component.test.js
index a33889f94..6d4612e3c 100644
--- a/ui/app/components/currency-input/tests/currency-input.component.test.js
+++ b/ui/app/components/ui/currency-input/tests/currency-input.component.test.js
@@ -1,4 +1,5 @@
import React from 'react'
+import PropTypes from 'prop-types'
import assert from 'assert'
import { shallow, mount } from 'enzyme'
import sinon from 'sinon'
@@ -32,7 +33,8 @@ describe('CurrencyInput Component', () => {
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
- suffix="ETH"
+ nativeSuffix="ETH"
+ fiatSuffix="USD"
nativeCurrency="ETH"
/>
</Provider>
@@ -58,7 +60,8 @@ describe('CurrencyInput Component', () => {
<Provider store={store}>
<CurrencyInput
value="de0b6b3a7640000"
- suffix="ETH"
+ fiatSuffix="USD"
+ nativeSuffix="ETH"
nativeCurrency="ETH"
currentCurrency="usd"
conversionRate={231.06}
@@ -90,7 +93,8 @@ describe('CurrencyInput Component', () => {
<Provider store={store}>
<CurrencyInput
value="f602f2234d0ea"
- suffix="USD"
+ fiatSuffix="USD"
+ nativeSuffix="ETH"
useFiat
nativeCurrency="ETH"
currentCurrency="usd"
@@ -108,6 +112,45 @@ describe('CurrencyInput Component', () => {
assert.equal(wrapper.find('.unit-input__input').props().value, '1')
assert.equal(wrapper.find('.currency-display-component').text(), '0.004328ETH')
})
+
+ it('should render properly with a native value when hideFiat is true', () => {
+ const mockStore = {
+ metamask: {
+ nativeCurrency: 'ETH',
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+
+ const wrapper = mount(
+ <Provider store={store}>
+ <CurrencyInput
+ value="f602f2234d0ea"
+ fiatSuffix="USD"
+ nativeSuffix="ETH"
+ useFiat
+ hideFiat={true}
+ nativeCurrency="ETH"
+ currentCurrency="usd"
+ conversionRate={231.06}
+ />
+ </Provider>,
+ {
+ context: { t: str => str + '_t' },
+ childContextTypes: { t: PropTypes.func },
+ }
+ )
+
+ assert.ok(wrapper)
+ const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
+ assert.equal(currencyInputInstance.state.decimalValue, 0.004328)
+ assert.equal(currencyInputInstance.state.hexValue, 'f602f2234d0ea')
+ assert.equal(wrapper.find('.unit-input__suffix').length, 1)
+ assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH')
+ assert.equal(wrapper.find('.unit-input__input').props().value, '0.004328')
+ assert.equal(wrapper.find('.currency-input__conversion-component').text(), 'noConversionRateAvailable_t')
+ })
})
describe('handling actions', () => {
@@ -247,5 +290,56 @@ describe('CurrencyInput Component', () => {
assert.equal(currencyInputInstance.state('hexValue'), '1ec05e43e72400')
assert.equal(currencyInputInstance.find(UnitInput).props().value, 2)
})
+
+ it('should swap selected currency when swap icon is clicked', () => {
+ const mockStore = {
+ metamask: {
+ nativeCurrency: 'ETH',
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+ const wrapper = mount(
+ <Provider store={store}>
+ <CurrencyInput
+ onChange={handleChangeSpy}
+ onBlur={handleBlurSpy}
+ nativeSuffix="ETH"
+ fiatSuffix="USD"
+ nativeCurrency="ETH"
+ currentCurrency="usd"
+ conversionRate={231.06}
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ assert.equal(handleChangeSpy.callCount, 0)
+ assert.equal(handleBlurSpy.callCount, 0)
+
+ const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
+ assert.equal(currencyInputInstance.state.decimalValue, 0)
+ assert.equal(currencyInputInstance.state.hexValue, undefined)
+ assert.equal(wrapper.find('.currency-display-component').text(), '$0.00USD')
+ const input = wrapper.find('input')
+ assert.equal(input.props().value, 0)
+
+ input.simulate('change', { target: { value: 1 } })
+ assert.equal(handleChangeSpy.callCount, 1)
+ assert.ok(handleChangeSpy.calledWith('de0b6b3a7640000'))
+ assert.equal(wrapper.find('.currency-display-component').text(), '$231.06USD')
+ assert.equal(currencyInputInstance.state.decimalValue, 1)
+ assert.equal(currencyInputInstance.state.hexValue, 'de0b6b3a7640000')
+
+ assert.equal(handleBlurSpy.callCount, 0)
+ input.simulate('blur')
+ assert.equal(handleBlurSpy.callCount, 1)
+ assert.ok(handleBlurSpy.calledWith('de0b6b3a7640000'))
+
+ const swap = wrapper.find('.currency-input__swap-component')
+ swap.simulate('click')
+ assert.equal(wrapper.find('.currency-display-component').text(), '0.004328ETH')
+ })
})
})
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
new file mode 100644
index 000000000..6109d29b6
--- /dev/null
+++ b/ui/app/components/ui/currency-input/tests/currency-input.container.test.js
@@ -0,0 +1,170 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+
+let mapStateToProps, mergeProps
+
+proxyquire('../currency-input.container.js', {
+ 'react-redux': {
+ connect: (ms, md, mp) => {
+ mapStateToProps = ms
+ mergeProps = mp
+ return () => ({})
+ },
+ },
+})
+
+describe('CurrencyInput container', () => {
+ describe('mapStateToProps()', () => {
+ const tests = [
+ // Test # 1
+ {
+ comment: 'should return correct props in mainnet',
+ mockState: {
+ metamask: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ preferences: {
+ showFiatInTestnets: false,
+ },
+ provider: {
+ type: 'mainnet',
+ },
+ },
+ },
+ expected: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ hideFiat: false,
+ },
+ },
+ // Test # 2
+ {
+ comment: 'should return correct props when not in mainnet and showFiatInTestnets is false',
+ mockState: {
+ metamask: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ preferences: {
+ showFiatInTestnets: false,
+ },
+ provider: {
+ type: 'rinkeby',
+ },
+ },
+ },
+ expected: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ hideFiat: true,
+ },
+ },
+ // Test # 3
+ {
+ comment: 'should return correct props when not in mainnet and showFiatInTestnets is true',
+ mockState: {
+ metamask: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ preferences: {
+ showFiatInTestnets: true,
+ },
+ provider: {
+ type: 'rinkeby',
+ },
+ },
+ },
+ expected: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ hideFiat: false,
+ },
+ },
+ // Test # 4
+ {
+ comment: 'should return correct props when in mainnet and showFiatInTestnets is true',
+ mockState: {
+ metamask: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ preferences: {
+ showFiatInTestnets: true,
+ },
+ provider: {
+ type: 'mainnet',
+ },
+ },
+ },
+ expected: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ hideFiat: false,
+ },
+ },
+ ]
+
+ tests.forEach(({ mockState, expected, comment }) => {
+ it(comment, () => assert.deepEqual(mapStateToProps(mockState), expected))
+ })
+ })
+
+ describe('mergeProps()', () => {
+ const tests = [
+ // Test # 1
+ {
+ comment: 'should return the correct props',
+ mock: {
+ stateProps: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ },
+ dispatchProps: {},
+ ownProps: {},
+ },
+ expected: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ // useFiat: true,
+ nativeSuffix: 'ETH',
+ fiatSuffix: 'USD',
+ },
+ },
+ // Test # 1
+ {
+ comment: 'should return the correct props when useFiat is true',
+ mock: {
+ stateProps: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ },
+ dispatchProps: {},
+ ownProps: { useFiat: true },
+ },
+ expected: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ useFiat: true,
+ nativeSuffix: 'ETH',
+ fiatSuffix: 'USD',
+ },
+ },
+ ]
+
+ tests.forEach(({ mock: { stateProps, dispatchProps, ownProps }, expected, comment }) => {
+ it(comment, () => {
+ assert.deepEqual(mergeProps(stateProps, dispatchProps, ownProps), expected)
+ })
+ })
+ })
+})
diff --git a/ui/app/components/editable-label.js b/ui/app/components/ui/editable-label.js
index eb41ec50c..eb41ec50c 100644
--- a/ui/app/components/editable-label.js
+++ b/ui/app/components/ui/editable-label.js
diff --git a/ui/app/components/error-message/error-message.component.js b/ui/app/components/ui/error-message/error-message.component.js
index b4464c33b..b4464c33b 100644
--- a/ui/app/components/error-message/error-message.component.js
+++ b/ui/app/components/ui/error-message/error-message.component.js
diff --git a/ui/app/components/error-message/index.js b/ui/app/components/ui/error-message/index.js
index 1c97a9955..1c97a9955 100644
--- a/ui/app/components/error-message/index.js
+++ b/ui/app/components/ui/error-message/index.js
diff --git a/ui/app/components/error-message/index.scss b/ui/app/components/ui/error-message/index.scss
index 5915e21cf..5915e21cf 100644
--- a/ui/app/components/error-message/index.scss
+++ b/ui/app/components/ui/error-message/index.scss
diff --git a/ui/app/components/error-message/tests/error-message.component.test.js b/ui/app/components/ui/error-message/tests/error-message.component.test.js
index 8c5347173..8c5347173 100644
--- a/ui/app/components/error-message/tests/error-message.component.test.js
+++ b/ui/app/components/ui/error-message/tests/error-message.component.test.js
diff --git a/ui/app/components/eth-balance.js b/ui/app/components/ui/eth-balance.js
index 2f6395a2d..7d577b716 100644
--- a/ui/app/components/eth-balance.js
+++ b/ui/app/components/ui/eth-balance.js
@@ -5,7 +5,7 @@ const { inherits } = require('util')
const {
formatBalance,
generateBalanceObject,
-} = require('../util')
+} = require('../../helpers/utils/util')
const Tooltip = require('./tooltip.js')
const FiatValue = require('./fiat-value.js')
diff --git a/ui/app/components/export-text-container/export-text-container.component.js b/ui/app/components/ui/export-text-container/export-text-container.component.js
index c2546fa9b..c632e8f26 100644
--- a/ui/app/components/export-text-container/export-text-container.component.js
+++ b/ui/app/components/ui/export-text-container/export-text-container.component.js
@@ -2,7 +2,7 @@ const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const copyToClipboard = require('copy-to-clipboard')
-const { exportAsFile } = require('../../util')
+const { exportAsFile } = require('../../../helpers/utils/util')
class ExportTextContainer extends Component {
render () {
diff --git a/ui/app/components/export-text-container/index.js b/ui/app/components/ui/export-text-container/index.js
index b2864a717..b2864a717 100644
--- a/ui/app/components/export-text-container/index.js
+++ b/ui/app/components/ui/export-text-container/index.js
diff --git a/ui/app/components/export-text-container/index.scss b/ui/app/components/ui/export-text-container/index.scss
index 975d62f70..975d62f70 100644
--- a/ui/app/components/export-text-container/index.scss
+++ b/ui/app/components/ui/export-text-container/index.scss
diff --git a/ui/app/components/fiat-value.js b/ui/app/components/ui/fiat-value.js
index 56465fc9d..02111ba49 100644
--- a/ui/app/components/fiat-value.js
+++ b/ui/app/components/ui/fiat-value.js
@@ -1,7 +1,7 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
-const formatBalance = require('../util').formatBalance
+const formatBalance = require('../../helpers/utils/util').formatBalance
module.exports = FiatValue
diff --git a/ui/app/components/hex-to-decimal/hex-to-decimal.component.js b/ui/app/components/ui/hex-to-decimal/hex-to-decimal.component.js
index 6847a6919..f03aaf255 100644
--- a/ui/app/components/hex-to-decimal/hex-to-decimal.component.js
+++ b/ui/app/components/ui/hex-to-decimal/hex-to-decimal.component.js
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import { hexToDecimal } from '../../helpers/conversions.util'
+import { hexToDecimal } from '../../../helpers/utils/conversions.util'
export default class HexToDecimal extends PureComponent {
static propTypes = {
diff --git a/ui/app/components/hex-to-decimal/index.js b/ui/app/components/ui/hex-to-decimal/index.js
index 6e8567ca9..6e8567ca9 100644
--- a/ui/app/components/hex-to-decimal/index.js
+++ b/ui/app/components/ui/hex-to-decimal/index.js
diff --git a/ui/app/components/hex-to-decimal/tests/hex-to-decimal.component.test.js b/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js
index c98da9ad4..c98da9ad4 100644
--- a/ui/app/components/hex-to-decimal/tests/hex-to-decimal.component.test.js
+++ b/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js
diff --git a/ui/app/components/identicon/identicon.component.js b/ui/app/components/ui/identicon/identicon.component.js
index b892e5ae5..88521247c 100644
--- a/ui/app/components/identicon/identicon.component.js
+++ b/ui/app/components/ui/identicon/identicon.component.js
@@ -1,9 +1,9 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-import { toDataUrl } from '../../../lib/blockies'
+import { toDataUrl } from '../../../../lib/blockies'
import contractMap from 'eth-contract-metadata'
-import { checksumAddress } from '../../../app/util'
+import { checksumAddress } from '../../../helpers/utils/util'
import Jazzicon from '../jazzicon'
const getStyles = diameter => (
diff --git a/ui/app/components/identicon/identicon.container.js b/ui/app/components/ui/identicon/identicon.container.js
index bc49bc18e..bc49bc18e 100644
--- a/ui/app/components/identicon/identicon.container.js
+++ b/ui/app/components/ui/identicon/identicon.container.js
diff --git a/ui/app/components/identicon/index.js b/ui/app/components/ui/identicon/index.js
index 799c886f2..799c886f2 100644
--- a/ui/app/components/identicon/index.js
+++ b/ui/app/components/ui/identicon/index.js
diff --git a/ui/app/components/identicon/index.scss b/ui/app/components/ui/identicon/index.scss
index 657afc48f..657afc48f 100644
--- a/ui/app/components/identicon/index.scss
+++ b/ui/app/components/ui/identicon/index.scss
diff --git a/ui/app/components/identicon/tests/identicon.component.test.js b/ui/app/components/ui/identicon/tests/identicon.component.test.js
index 2944818f5..2944818f5 100644
--- a/ui/app/components/identicon/tests/identicon.component.test.js
+++ b/ui/app/components/ui/identicon/tests/identicon.component.test.js
diff --git a/ui/app/components/jazzicon/index.js b/ui/app/components/ui/jazzicon/index.js
index bea900ab9..bea900ab9 100644
--- a/ui/app/components/jazzicon/index.js
+++ b/ui/app/components/ui/jazzicon/index.js
diff --git a/ui/app/components/jazzicon/jazzicon.component.js b/ui/app/components/ui/jazzicon/jazzicon.component.js
index fcb1c59b1..3a17e446f 100644
--- a/ui/app/components/jazzicon/jazzicon.component.js
+++ b/ui/app/components/ui/jazzicon/jazzicon.component.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import isNode from 'detect-node'
import { findDOMNode } from 'react-dom'
import jazzicon from 'jazzicon'
-import iconFactoryGenerator from '../../../lib/icon-factory'
+import iconFactoryGenerator from '../../../../lib/icon-factory'
const iconFactory = iconFactoryGenerator(jazzicon)
/**
diff --git a/ui/app/components/loading-screen/index.js b/ui/app/components/ui/loading-screen/index.js
index 191d953f7..191d953f7 100644
--- a/ui/app/components/loading-screen/index.js
+++ b/ui/app/components/ui/loading-screen/index.js
diff --git a/ui/app/components/loading-screen/loading-screen.component.js b/ui/app/components/ui/loading-screen/loading-screen.component.js
index 6b843cfee..6b843cfee 100644
--- a/ui/app/components/loading-screen/loading-screen.component.js
+++ b/ui/app/components/ui/loading-screen/loading-screen.component.js
diff --git a/ui/app/components/ui/lock-icon/index.js b/ui/app/components/ui/lock-icon/index.js
new file mode 100644
index 000000000..6b4df0e58
--- /dev/null
+++ b/ui/app/components/ui/lock-icon/index.js
@@ -0,0 +1 @@
+export { default } from './lock-icon.component'
diff --git a/ui/app/components/ui/lock-icon/lock-icon.component.js b/ui/app/components/ui/lock-icon/lock-icon.component.js
new file mode 100644
index 000000000..d010cb6b2
--- /dev/null
+++ b/ui/app/components/ui/lock-icon/lock-icon.component.js
@@ -0,0 +1,32 @@
+import React from 'react'
+
+export default function LockIcon (props) {
+ return (
+ <svg
+ version="1.1"
+ id="Capa_1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlnsXlink="http://www.w3.org/1999/xlink"
+ x="0px"
+ y="0px"
+ width="401.998px"
+ height="401.998px"
+ viewBox="0 0 401.998 401.998"
+ style={{enableBackground: 'new 0 0 401.998 401.998'}}
+ xmlSpace="preserve"
+ {...props}
+ >
+ <g>
+ <path
+ d="M357.45,190.721c-5.331-5.33-11.8-7.993-19.417-7.993h-9.131v-54.821c0-35.022-12.559-65.093-37.685-90.218
+ C266.093,12.563,236.025,0,200.998,0c-35.026,0-65.1,12.563-90.222,37.688C85.65,62.814,73.091,92.884,73.091,127.907v54.821
+ h-9.135c-7.611,0-14.084,2.663-19.414,7.993c-5.33,5.326-7.994,11.799-7.994,19.417V374.59c0,7.611,2.665,14.086,7.994,19.417
+ c5.33,5.325,11.803,7.991,19.414,7.991H338.04c7.617,0,14.085-2.663,19.417-7.991c5.325-5.331,7.994-11.806,7.994-19.417V210.135
+ C365.455,202.523,362.782,196.051,357.45,190.721z M274.087,182.728H127.909v-54.821c0-20.175,7.139-37.402,21.414-51.675
+ c14.277-14.275,31.501-21.411,51.678-21.411c20.179,0,37.399,7.135,51.677,21.411c14.271,14.272,21.409,31.5,21.409,51.675V182.728
+ z"
+ />
+ </g>
+ </svg>
+ )
+}
diff --git a/ui/app/components/mascot.js b/ui/app/components/ui/mascot.js
index 3b0d3e31b..3b0d3e31b 100644
--- a/ui/app/components/mascot.js
+++ b/ui/app/components/ui/mascot.js
diff --git a/ui/app/components/page-container/index.js b/ui/app/components/ui/page-container/index.js
index 913b8c9c6..913b8c9c6 100644
--- a/ui/app/components/page-container/index.js
+++ b/ui/app/components/ui/page-container/index.js
diff --git a/ui/app/components/page-container/index.scss b/ui/app/components/ui/page-container/index.scss
index ba1215e84..b71a3cb9d 100644
--- a/ui/app/components/page-container/index.scss
+++ b/ui/app/components/ui/page-container/index.scss
@@ -6,6 +6,7 @@
display: flex;
flex-flow: column;
border-radius: 8px;
+ overflow-y: auto;
&__header {
display: flex;
@@ -41,6 +42,12 @@
justify-content: space-between;
}
+ &__bottom {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ }
+
&__footer {
display: flex;
flex-flow: column;
@@ -194,10 +201,10 @@
.page-container {
height: 100%;
width: 100%;
- overflow-y: auto;
background-color: $white;
border-radius: 0;
flex: 1;
+ overflow-y: auto;
}
}
diff --git a/ui/app/components/page-container/page-container-content.component.js b/ui/app/components/ui/page-container/page-container-content.component.js
index a1d6988cc..a1d6988cc 100644
--- a/ui/app/components/page-container/page-container-content.component.js
+++ b/ui/app/components/ui/page-container/page-container-content.component.js
diff --git a/ui/app/components/page-container/page-container-footer/index.js b/ui/app/components/ui/page-container/page-container-footer/index.js
index 7825c4520..7825c4520 100644
--- a/ui/app/components/page-container/page-container-footer/index.js
+++ b/ui/app/components/ui/page-container/page-container-footer/index.js
diff --git a/ui/app/components/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 773fe1f56..85b16cefe 100644
--- a/ui/app/components/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
@@ -12,6 +12,7 @@ export default class PageContainerFooter extends Component {
submitText: PropTypes.string,
disabled: PropTypes.bool,
submitButtonType: PropTypes.string,
+ hideCancel: PropTypes.bool,
}
static contextTypes = {
@@ -27,20 +28,21 @@ export default class PageContainerFooter extends Component {
submitText,
disabled,
submitButtonType,
+ hideCancel,
} = this.props
return (
<div className="page-container__footer">
<header>
- <Button
+ {!hideCancel && <Button
type="default"
large
className="page-container__footer-button"
onClick={e => onCancel(e)}
>
{ cancelText || this.context.t('cancel') }
- </Button>
+ </Button>}
<Button
type={submitButtonType || 'primary'}
diff --git a/ui/app/components/page-container/page-container-footer/tests/page-container-footer.component.test.js b/ui/app/components/ui/page-container/page-container-footer/tests/page-container-footer.component.test.js
index 64efabab0..64efabab0 100644
--- a/ui/app/components/page-container/page-container-footer/tests/page-container-footer.component.test.js
+++ b/ui/app/components/ui/page-container/page-container-footer/tests/page-container-footer.component.test.js
diff --git a/ui/app/components/page-container/page-container-header/index.js b/ui/app/components/ui/page-container/page-container-header/index.js
index b194af057..b194af057 100644
--- a/ui/app/components/page-container/page-container-header/index.js
+++ b/ui/app/components/ui/page-container/page-container-header/index.js
diff --git a/ui/app/components/page-container/page-container-header/page-container-header.component.js b/ui/app/components/ui/page-container/page-container-header/page-container-header.component.js
index a8458604e..08f9c7544 100644
--- a/ui/app/components/page-container/page-container-header/page-container-header.component.js
+++ b/ui/app/components/ui/page-container/page-container-header/page-container-header.component.js
@@ -12,6 +12,7 @@ export default class PageContainerHeader extends Component {
backButtonStyles: PropTypes.object,
backButtonString: PropTypes.string,
tabs: PropTypes.node,
+ headerCloseText: PropTypes.string,
}
renderTabs () {
@@ -41,7 +42,7 @@ export default class PageContainerHeader extends Component {
}
render () {
- const { title, subtitle, onClose, tabs } = this.props
+ const { title, subtitle, onClose, tabs, headerCloseText } = this.props
return (
<div className={
@@ -66,10 +67,12 @@ export default class PageContainerHeader extends Component {
}
{
- onClose && <div
- className="page-container__header-close"
- onClick={() => onClose()}
- />
+ onClose && headerCloseText
+ ? <div className="page-container__header-close-text" onClick={() => onClose()}>{ headerCloseText }</div>
+ : onClose && <div
+ className="page-container__header-close"
+ onClick={() => onClose()}
+ />
}
{ this.renderTabs() }
diff --git a/ui/app/components/page-container/page-container-header/tests/page-container-header.component.test.js b/ui/app/components/ui/page-container/page-container-header/tests/page-container-header.component.test.js
index 59304b2bd..59304b2bd 100644
--- a/ui/app/components/page-container/page-container-header/tests/page-container-header.component.test.js
+++ b/ui/app/components/ui/page-container/page-container-header/tests/page-container-header.component.test.js
diff --git a/ui/app/components/page-container/page-container.component.js b/ui/app/components/ui/page-container/page-container.component.js
index 3a2274a29..45dfff517 100644
--- a/ui/app/components/page-container/page-container.component.js
+++ b/ui/app/components/ui/page-container/page-container.component.js
@@ -9,6 +9,7 @@ export default class PageContainer extends PureComponent {
// PageContainerHeader props
backButtonString: PropTypes.string,
backButtonStyles: PropTypes.object,
+ headerCloseText: PropTypes.string,
onBackButtonClick: PropTypes.func,
onClose: PropTypes.func,
showBackButton: PropTypes.bool,
@@ -22,6 +23,7 @@ export default class PageContainer extends PureComponent {
// PageContainerFooter props
cancelText: PropTypes.string,
disabled: PropTypes.bool,
+ hideCancel: PropTypes.bool,
onCancel: PropTypes.func,
onSubmit: PropTypes.func,
submitText: PropTypes.string,
@@ -58,7 +60,8 @@ export default class PageContainer extends PureComponent {
renderActiveTabContent () {
const { tabsComponent } = this.props
- const { children } = tabsComponent.props
+ let { children } = tabsComponent.props
+ children = children.filter(child => child)
const { activeTabIndex } = this.state
return children[activeTabIndex]
@@ -92,6 +95,8 @@ export default class PageContainer extends PureComponent {
onSubmit,
submitText,
disabled,
+ headerCloseText,
+ hideCancel,
} = this.props
return (
@@ -105,17 +110,21 @@ export default class PageContainer extends PureComponent {
backButtonStyles={backButtonStyles}
backButtonString={backButtonString}
tabs={this.renderTabs()}
+ headerCloseText={headerCloseText}
/>
- <div className="page-container__content">
- { this.renderContent() }
+ <div className="page-container__bottom">
+ <div className="page-container__content">
+ { this.renderContent() }
+ </div>
+ <PageContainerFooter
+ onCancel={onCancel}
+ cancelText={cancelText}
+ hideCancel={hideCancel}
+ onSubmit={onSubmit}
+ submitText={submitText}
+ disabled={disabled}
+ />
</div>
- <PageContainerFooter
- onCancel={onCancel}
- cancelText={cancelText}
- onSubmit={onSubmit}
- submitText={submitText}
- disabled={disabled}
- />
</div>
)
}
diff --git a/ui/app/components/send/account-list-item/account-list-item.scss b/ui/app/components/ui/page-container/tests/page-container.component.test.js
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/send/account-list-item/account-list-item.scss
+++ b/ui/app/components/ui/page-container/tests/page-container.component.test.js
diff --git a/ui/app/components/qr-code.js b/ui/app/components/ui/qr-code.js
index d3242ddf5..351e072e2 100644
--- a/ui/app/components/qr-code.js
+++ b/ui/app/components/ui/qr-code.js
@@ -1,11 +1,11 @@
const Component = require('react').Component
const h = require('react-hyperscript')
-const qrCode = require('qrcode-npm').qrcode
+const qrCode = require('qrcode-generator')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const { isHexPrefixed } = require('ethereumjs-util')
const ReadOnlyInput = require('./readonly-input')
-const { checksumAddress } = require('../util')
+const { checksumAddress } = require('../../helpers/utils/util')
module.exports = connect(mapStateToProps)(QrCodeView)
@@ -25,8 +25,8 @@ function QrCodeView () {
QrCodeView.prototype.render = function () {
const props = this.props
- const { message, data } = props.Qr
- const address = `${isHexPrefixed(data) ? 'ethereum:' : ''}${checksumAddress(data)}`
+ const { message, data, network } = props.Qr
+ const address = `${isHexPrefixed(data) ? 'ethereum:' : ''}${checksumAddress(data, network)}`
const qrImage = qrCode(4, 'M')
qrImage.addData(address)
qrImage.make()
@@ -51,7 +51,7 @@ QrCodeView.prototype.render = function () {
h(ReadOnlyInput, {
wrapperClass: 'ellip-address-wrapper',
inputClass: 'qr-ellip-address',
- value: checksumAddress(data),
+ value: checksumAddress(data, network),
}),
])
}
diff --git a/ui/app/components/readonly-input.js b/ui/app/components/ui/readonly-input.js
index fcf05fb9e..fcf05fb9e 100644
--- a/ui/app/components/readonly-input.js
+++ b/ui/app/components/ui/readonly-input.js
diff --git a/ui/app/components/sender-to-recipient/index.js b/ui/app/components/ui/sender-to-recipient/index.js
index f515c4ac4..f515c4ac4 100644
--- a/ui/app/components/sender-to-recipient/index.js
+++ b/ui/app/components/ui/sender-to-recipient/index.js
diff --git a/ui/app/components/sender-to-recipient/index.scss b/ui/app/components/ui/sender-to-recipient/index.scss
index 0ab0413be..b21e4e1bb 100644
--- a/ui/app/components/sender-to-recipient/index.scss
+++ b/ui/app/components/ui/sender-to-recipient/index.scss
@@ -1,12 +1,13 @@
.sender-to-recipient {
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ position: relative;
+ flex: 0 0 auto;
+
&--default {
- width: 100%;
- display: flex;
- flex-direction: row;
- justify-content: center;
border-bottom: 1px solid $geyser;
- position: relative;
- flex: 0 0 auto;
height: 42px;
.sender-to-recipient {
@@ -74,13 +75,6 @@
}
&--cards {
- width: 100%;
- display: flex;
- flex-direction: row;
- justify-content: center;
- position: relative;
- flex: 0 0 auto;
-
.sender-to-recipient {
&__party {
display: flex;
@@ -117,4 +111,39 @@
}
}
}
+
+ &--flat {
+ .sender-to-recipient {
+ &__party {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ flex: 1;
+ padding: 6px;
+ cursor: pointer;
+ min-width: 0;
+ color: $dusty-gray;
+ }
+
+ &__tooltip-wrapper {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ &__name {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: .6875rem;
+ }
+
+ &__arrow-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+ }
+ }
}
diff --git a/ui/app/components/sender-to-recipient/sender-to-recipient.component.js b/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js
index e71bd7406..57b595d48 100644
--- a/ui/app/components/sender-to-recipient/sender-to-recipient.component.js
+++ b/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js
@@ -4,12 +4,13 @@ import classnames from 'classnames'
import Identicon from '../identicon'
import Tooltip from '../tooltip-v2'
import copyToClipboard from 'copy-to-clipboard'
-import { DEFAULT_VARIANT, CARDS_VARIANT } from './sender-to-recipient.constants'
-import { checksumAddress } from '../../util'
+import { DEFAULT_VARIANT, CARDS_VARIANT, FLAT_VARIANT } from './sender-to-recipient.constants'
+import { checksumAddress } from '../../../helpers/utils/util'
const variantHash = {
[DEFAULT_VARIANT]: 'sender-to-recipient--default',
[CARDS_VARIANT]: 'sender-to-recipient--cards',
+ [FLAT_VARIANT]: 'sender-to-recipient--flat',
}
export default class SenderToRecipient extends PureComponent {
@@ -19,9 +20,11 @@ export default class SenderToRecipient extends PureComponent {
recipientName: PropTypes.string,
recipientAddress: PropTypes.string,
t: PropTypes.func,
- variant: PropTypes.oneOf([DEFAULT_VARIANT, CARDS_VARIANT]),
+ variant: PropTypes.oneOf([DEFAULT_VARIANT, CARDS_VARIANT, FLAT_VARIANT]),
addressOnly: PropTypes.bool,
assetImage: PropTypes.string,
+ onRecipientClick: PropTypes.func,
+ onSenderClick: PropTypes.func,
}
static defaultProps = {
@@ -85,7 +88,7 @@ export default class SenderToRecipient extends PureComponent {
renderRecipientWithAddress () {
const { t } = this.context
- const { recipientName, recipientAddress, addressOnly } = this.props
+ const { recipientName, recipientAddress, addressOnly, onRecipientClick } = this.props
const checksummedRecipientAddress = checksumAddress(recipientAddress)
return (
@@ -94,6 +97,9 @@ export default class SenderToRecipient extends PureComponent {
onClick={() => {
this.setState({ recipientAddressCopied: true })
copyToClipboard(checksummedRecipientAddress)
+ if (onRecipientClick) {
+ onRecipientClick()
+ }
}}
>
{ this.renderRecipientIdenticon() }
@@ -128,16 +134,9 @@ export default class SenderToRecipient extends PureComponent {
}
renderArrow () {
- return this.props.variant === CARDS_VARIANT
+ return this.props.variant === DEFAULT_VARIANT
? (
<div className="sender-to-recipient__arrow-container">
- <img
- height={20}
- src="./images/caret-right.svg"
- />
- </div>
- ) : (
- <div className="sender-to-recipient__arrow-container">
<div className="sender-to-recipient__arrow-circle">
<img
height={15}
@@ -146,20 +145,30 @@ export default class SenderToRecipient extends PureComponent {
/>
</div>
</div>
+ ) : (
+ <div className="sender-to-recipient__arrow-container">
+ <img
+ height={20}
+ src="./images/caret-right.svg"
+ />
+ </div>
)
}
render () {
- const { senderAddress, recipientAddress, variant } = this.props
+ const { senderAddress, recipientAddress, variant, onSenderClick } = this.props
const checksummedSenderAddress = checksumAddress(senderAddress)
return (
- <div className={classnames(variantHash[variant])}>
+ <div className={classnames('sender-to-recipient', variantHash[variant])}>
<div
className={classnames('sender-to-recipient__party sender-to-recipient__party--sender')}
onClick={() => {
this.setState({ senderAddressCopied: true })
copyToClipboard(checksummedSenderAddress)
+ if (onSenderClick) {
+ onSenderClick()
+ }
}}
>
{ this.renderSenderIdenticon() }
diff --git a/ui/app/components/sender-to-recipient/sender-to-recipient.constants.js b/ui/app/components/ui/sender-to-recipient/sender-to-recipient.constants.js
index 166228932..f53a5115d 100644
--- a/ui/app/components/sender-to-recipient/sender-to-recipient.constants.js
+++ b/ui/app/components/ui/sender-to-recipient/sender-to-recipient.constants.js
@@ -1,3 +1,4 @@
// Component design variants
export const DEFAULT_VARIANT = 'DEFAULT_VARIANT'
export const CARDS_VARIANT = 'CARDS_VARIANT'
+export const FLAT_VARIANT = 'FLAT_VARIANT'
diff --git a/ui/app/components/spinner/index.js b/ui/app/components/ui/spinner/index.js
index 9589efcf0..9589efcf0 100644
--- a/ui/app/components/spinner/index.js
+++ b/ui/app/components/ui/spinner/index.js
diff --git a/ui/app/components/spinner/spinner.component.js b/ui/app/components/ui/spinner/spinner.component.js
index b9a2eb52a..b9a2eb52a 100644
--- a/ui/app/components/spinner/spinner.component.js
+++ b/ui/app/components/ui/spinner/spinner.component.js
diff --git a/ui/app/components/tabs/index.js b/ui/app/components/ui/tabs/index.js
index 3a8d18248..3a8d18248 100644
--- a/ui/app/components/tabs/index.js
+++ b/ui/app/components/ui/tabs/index.js
diff --git a/ui/app/components/tabs/index.scss b/ui/app/components/ui/tabs/index.scss
index a3b42f8e3..25143ff35 100644
--- a/ui/app/components/tabs/index.scss
+++ b/ui/app/components/ui/tabs/index.scss
@@ -1,4 +1,4 @@
-@import './tab/index';
+@import 'tab/index';
.tabs {
&__list {
diff --git a/ui/app/components/tabs/tab/index.js b/ui/app/components/ui/tabs/tab/index.js
index fbc309e8e..fbc309e8e 100644
--- a/ui/app/components/tabs/tab/index.js
+++ b/ui/app/components/ui/tabs/tab/index.js
diff --git a/ui/app/components/tabs/tab/index.scss b/ui/app/components/ui/tabs/tab/index.scss
index 1de6ffa0e..1de6ffa0e 100644
--- a/ui/app/components/tabs/tab/index.scss
+++ b/ui/app/components/ui/tabs/tab/index.scss
diff --git a/ui/app/components/tabs/tab/tab.component.js b/ui/app/components/ui/tabs/tab/tab.component.js
index 9e590391c..9e590391c 100644
--- a/ui/app/components/tabs/tab/tab.component.js
+++ b/ui/app/components/ui/tabs/tab/tab.component.js
diff --git a/ui/app/components/tabs/tabs.component.js b/ui/app/components/ui/tabs/tabs.component.js
index d26dcff2f..d26dcff2f 100644
--- a/ui/app/components/tabs/tabs.component.js
+++ b/ui/app/components/ui/tabs/tabs.component.js
diff --git a/ui/app/components/text-field/index.js b/ui/app/components/ui/text-field/index.js
index 171caf7a4..171caf7a4 100644
--- a/ui/app/components/text-field/index.js
+++ b/ui/app/components/ui/text-field/index.js
diff --git a/ui/app/components/text-field/text-field.component.js b/ui/app/components/ui/text-field/text-field.component.js
index 2c72d8124..2c72d8124 100644
--- a/ui/app/components/text-field/text-field.component.js
+++ b/ui/app/components/ui/text-field/text-field.component.js
diff --git a/ui/app/components/text-field/text-field.stories.js b/ui/app/components/ui/text-field/text-field.stories.js
index c00873b8a..337f78ecf 100644
--- a/ui/app/components/text-field/text-field.stories.js
+++ b/ui/app/components/ui/text-field/text-field.stories.js
@@ -1,6 +1,6 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
-import TextField from './'
+import TextField from '.'
storiesOf('TextField', module)
.add('text', () =>
diff --git a/ui/app/components/token-balance/index.js b/ui/app/components/ui/token-balance/index.js
index f7da15cf8..f7da15cf8 100644
--- a/ui/app/components/token-balance/index.js
+++ b/ui/app/components/ui/token-balance/index.js
diff --git a/ui/app/components/ui/token-balance/index.scss b/ui/app/components/ui/token-balance/index.scss
new file mode 100644
index 000000000..2ff6dfbc8
--- /dev/null
+++ b/ui/app/components/ui/token-balance/index.scss
@@ -0,0 +1,14 @@
+.token-balance-component {
+ display: flex;
+ align-items: center;
+
+ &__text {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &__suffix {
+ padding-left: 4px;
+ }
+}
diff --git a/ui/app/components/token-balance/token-balance.component.js b/ui/app/components/ui/token-balance/token-balance.component.js
index 2b4f73980..af1a32578 100644
--- a/ui/app/components/token-balance/token-balance.component.js
+++ b/ui/app/components/ui/token-balance/token-balance.component.js
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import classnames from 'classnames'
+import CurrencyDisplay from '../currency-display'
export default class TokenBalance extends PureComponent {
static propTypes = {
@@ -12,12 +12,14 @@ export default class TokenBalance extends PureComponent {
}
render () {
- const { className, string, withSymbol, symbol } = this.props
+ const { className, string, symbol } = this.props
return (
- <div className={classnames('hide-text-overflow', className)}>
- { string + (withSymbol ? ` ${symbol}` : '') }
- </div>
+ <CurrencyDisplay
+ className={className}
+ displayValue={string}
+ suffix={symbol}
+ />
)
}
}
diff --git a/ui/app/components/token-balance/token-balance.container.js b/ui/app/components/ui/token-balance/token-balance.container.js
index adc001f83..a0f1efc20 100644
--- a/ui/app/components/token-balance/token-balance.container.js
+++ b/ui/app/components/ui/token-balance/token-balance.container.js
@@ -1,8 +1,8 @@
import { connect } from 'react-redux'
import { compose } from 'recompose'
-import withTokenTracker from '../../higher-order-components/with-token-tracker'
+import withTokenTracker from '../../../helpers/higher-order-components/with-token-tracker'
import TokenBalance from './token-balance.component'
-import selectors from '../../selectors'
+import selectors from '../../../selectors/selectors'
const mapStateToProps = state => {
return {
diff --git a/ui/app/components/token-currency-display/index.js b/ui/app/components/ui/token-currency-display/index.js
index 6065cae1f..6065cae1f 100644
--- a/ui/app/components/token-currency-display/index.js
+++ b/ui/app/components/ui/token-currency-display/index.js
diff --git a/ui/app/components/token-currency-display/token-currency-display.component.js b/ui/app/components/ui/token-currency-display/token-currency-display.component.js
index 4bb09a4b6..3c2722b36 100644
--- a/ui/app/components/token-currency-display/token-currency-display.component.js
+++ b/ui/app/components/ui/token-currency-display/token-currency-display.component.js
@@ -1,8 +1,8 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import CurrencyDisplay from '../currency-display/currency-display.component'
-import { getTokenData } from '../../helpers/transactions.util'
-import { getTokenValue, calcTokenAmount } from '../../token-util'
+import CurrencyDisplay from '../currency-display'
+import { getTokenData } from '../../../helpers/utils/transactions.util'
+import { getTokenValue, calcTokenAmount } from '../../../helpers/utils/token-util'
export default class TokenCurrencyDisplay extends PureComponent {
static propTypes = {
@@ -12,6 +12,7 @@ export default class TokenCurrencyDisplay extends PureComponent {
state = {
displayValue: '',
+ suffix: '',
}
componentDidMount () {
@@ -29,25 +30,27 @@ export default class TokenCurrencyDisplay extends PureComponent {
setDisplayValue () {
const { transactionData: data, token } = this.props
- const { decimals = '', symbol = '' } = token
+ const { decimals = '', symbol: suffix = '' } = token
const tokenData = getTokenData(data)
let displayValue
- if (tokenData.params && tokenData.params.length) {
+ if (tokenData && tokenData.params && tokenData.params.length) {
const tokenValue = getTokenValue(tokenData.params)
- const tokenAmount = calcTokenAmount(tokenValue, decimals)
- displayValue = `${tokenAmount} ${symbol}`
+ displayValue = calcTokenAmount(tokenValue, decimals).toString()
}
- this.setState({ displayValue })
+ this.setState({ displayValue, suffix })
}
render () {
+ const { displayValue, suffix } = this.state
+
return (
<CurrencyDisplay
{...this.props}
- displayValue={this.state.displayValue}
+ displayValue={displayValue}
+ suffix={suffix}
/>
)
}
diff --git a/ui/app/components/token-input/index.js b/ui/app/components/ui/token-input/index.js
index 22c06111e..22c06111e 100644
--- a/ui/app/components/token-input/index.js
+++ b/ui/app/components/ui/token-input/index.js
diff --git a/ui/app/components/token-input/tests/token-input.component.test.js b/ui/app/components/ui/token-input/tests/token-input.component.test.js
index 2dacb9bc4..881101880 100644
--- a/ui/app/components/token-input/tests/token-input.component.test.js
+++ b/ui/app/components/ui/token-input/tests/token-input.component.test.js
@@ -159,6 +159,48 @@ describe('TokenInput Component', () => {
assert.equal(wrapper.find('.unit-input__input').props().value, '1')
assert.equal(wrapper.find('.currency-display-component').text(), '$462.12USD')
})
+
+ it('should render properly with a token value for fiat, but hideConversion is true', () => {
+ const mockStore = {
+ metamask: {
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+
+ const wrapper = mount(
+ <Provider store={store}>
+ <TokenInput
+ value="2710"
+ selectedToken={{
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ }}
+ suffix="ABC"
+ selectedTokenExchangeRate={2}
+ showFiat
+ hideConversion
+ />
+ </Provider>,
+ {
+ context: { t },
+ childContextTypes: {
+ t: PropTypes.func,
+ },
+ },
+ )
+
+ assert.ok(wrapper)
+ const tokenInputInstance = wrapper.find(TokenInput).at(0).instance()
+ assert.equal(tokenInputInstance.state.decimalValue, 1)
+ assert.equal(tokenInputInstance.state.hexValue, '2710')
+ assert.equal(wrapper.find('.unit-input__suffix').length, 1)
+ assert.equal(wrapper.find('.unit-input__suffix').text(), 'ABC')
+ assert.equal(wrapper.find('.unit-input__input').props().value, '1')
+ assert.equal(wrapper.find('.currency-input__conversion-component').text(), 'translate noConversionRateAvailable')
+ })
})
describe('handling actions', () => {
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
new file mode 100644
index 000000000..2b1c102c8
--- /dev/null
+++ b/ui/app/components/ui/token-input/tests/token-input.container.test.js
@@ -0,0 +1,255 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+
+let mapStateToProps, mergeProps
+
+proxyquire('../token-input.container.js', {
+ 'react-redux': {
+ connect: (ms, md, mp) => {
+ mapStateToProps = ms
+ mergeProps = mp
+ return () => ({})
+ },
+ },
+})
+
+describe('TokenInput container', () => {
+ describe('mapStateToProps()', () => {
+ it('should return the correct props when send is empty', () => {
+ const mockState = {
+ metamask: {
+ currentCurrency: 'usd',
+ tokens: [
+ {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ ],
+ selectedTokenAddress: '0x1',
+ contractExchangeRates: {},
+ send: {},
+ preferences: {
+ showFiatInTestnets: false,
+ },
+ provider: {
+ type: 'mainnet',
+ },
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ currentCurrency: 'usd',
+ selectedToken: {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ selectedTokenExchangeRate: 0,
+ hideConversion: false,
+ })
+ })
+
+ it('should return the correct props when selectedTokenAddress is not found and send is populated', () => {
+ const mockState = {
+ metamask: {
+ currentCurrency: 'usd',
+ tokens: [
+ {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ ],
+ selectedTokenAddress: '0x2',
+ contractExchangeRates: {},
+ send: {
+ token: { address: 'test' },
+ },
+ preferences: {
+ showFiatInTestnets: false,
+ },
+ provider: {
+ type: 'mainnet',
+ },
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ currentCurrency: 'usd',
+ selectedToken: {
+ address: 'test',
+ },
+ selectedTokenExchangeRate: 0,
+ hideConversion: false,
+ })
+ })
+
+ it('should return the correct props when contractExchangeRates is populated', () => {
+ const mockState = {
+ metamask: {
+ currentCurrency: 'usd',
+ tokens: [
+ {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ ],
+ selectedTokenAddress: '0x1',
+ contractExchangeRates: {
+ '0x1': 5,
+ },
+ send: {},
+ preferences: {
+ showFiatInTestnets: false,
+ },
+ provider: {
+ type: 'mainnet',
+ },
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ currentCurrency: 'usd',
+ selectedToken: {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ selectedTokenExchangeRate: 5,
+ hideConversion: false,
+ })
+ })
+
+ it('should return the correct props when not in mainnet and showFiatInTestnets is false', () => {
+ const mockState = {
+ metamask: {
+ currentCurrency: 'usd',
+ tokens: [
+ {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ ],
+ selectedTokenAddress: '0x1',
+ contractExchangeRates: {},
+ send: {},
+ preferences: {
+ showFiatInTestnets: false,
+ },
+ provider: {
+ type: 'rinkeby',
+ },
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ currentCurrency: 'usd',
+ selectedToken: {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ selectedTokenExchangeRate: 0,
+ hideConversion: true,
+ })
+ })
+
+ it('should return the correct props when not in mainnet and showFiatInTestnets is true', () => {
+ const mockState = {
+ metamask: {
+ currentCurrency: 'usd',
+ tokens: [
+ {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ ],
+ selectedTokenAddress: '0x1',
+ contractExchangeRates: {},
+ send: {},
+ preferences: {
+ showFiatInTestnets: true,
+ },
+ provider: {
+ type: 'rinkeby',
+ },
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ currentCurrency: 'usd',
+ selectedToken: {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ selectedTokenExchangeRate: 0,
+ hideConversion: false,
+ })
+ })
+
+ it('should return the correct props when in mainnet and showFiatInTestnets is true', () => {
+ const mockState = {
+ metamask: {
+ currentCurrency: 'usd',
+ tokens: [
+ {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ ],
+ selectedTokenAddress: '0x1',
+ contractExchangeRates: {},
+ send: {},
+ preferences: {
+ showFiatInTestnets: true,
+ },
+ provider: {
+ type: 'mainnet',
+ },
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ currentCurrency: 'usd',
+ selectedToken: {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ selectedTokenExchangeRate: 0,
+ hideConversion: false,
+ })
+ })
+ })
+
+ describe('mergeProps()', () => {
+ it('should return the correct props', () => {
+ const mockStateProps = {
+ currentCurrency: 'usd',
+ selectedToken: {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ selectedTokenExchangeRate: 5,
+ }
+
+ assert.deepEqual(mergeProps(mockStateProps, {}, {}), {
+ currentCurrency: 'usd',
+ selectedToken: {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ selectedTokenExchangeRate: 5,
+ suffix: 'ABC',
+ })
+ })
+ })
+})
diff --git a/ui/app/components/token-input/token-input.component.js b/ui/app/components/ui/token-input/token-input.component.js
index d1388945b..c28af5fde 100644
--- a/ui/app/components/token-input/token-input.component.js
+++ b/ui/app/components/ui/token-input/token-input.component.js
@@ -2,10 +2,10 @@ import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import UnitInput from '../unit-input'
import CurrencyDisplay from '../currency-display'
-import { getWeiHexFromDecimalValue } from '../../helpers/conversions.util'
+import { getWeiHexFromDecimalValue } from '../../../helpers/utils/conversions.util'
import ethUtil from 'ethereumjs-util'
-import { conversionUtil, multiplyCurrencies } from '../../conversion-util'
-import { ETH } from '../../constants/common'
+import { conversionUtil, multiplyCurrencies } from '../../../helpers/utils/conversion-util'
+import { ETH } from '../../../helpers/constants/common'
/**
* Component that allows user to enter token values as a number, and props receive a converted
@@ -24,6 +24,7 @@ export default class TokenInput extends PureComponent {
value: PropTypes.string,
suffix: PropTypes.string,
showFiat: PropTypes.bool,
+ hideConversion: PropTypes.bool,
selectedToken: PropTypes.object,
selectedTokenExchangeRate: PropTypes.number,
}
@@ -32,7 +33,7 @@ export default class TokenInput extends PureComponent {
super(props)
const { value: hexValue } = props
- const decimalValue = hexValue ? this.getDecimalValue(props) : 0
+ const decimalValue = hexValue ? this.getValue(props) : 0
this.state = {
decimalValue,
@@ -46,12 +47,12 @@ export default class TokenInput extends PureComponent {
const { hexValue: stateHexValue } = this.state
if (prevPropsHexValue !== propsHexValue && propsHexValue !== stateHexValue) {
- const decimalValue = this.getDecimalValue(this.props)
+ const decimalValue = this.getValue(this.props)
this.setState({ hexValue: propsHexValue, decimalValue })
}
}
- getDecimalValue (props) {
+ getValue (props) {
const { value: hexValue, selectedToken: { decimals, symbol } = {} } = props
const multiplier = Math.pow(10, Number(decimals || 0))
@@ -63,7 +64,7 @@ export default class TokenInput extends PureComponent {
invertConversionRate: true,
})
- return Number(decimalValueString) || 0
+ return Number(decimalValueString) ? decimalValueString : ''
}
handleChange = decimalValue => {
@@ -81,10 +82,18 @@ export default class TokenInput extends PureComponent {
}
renderConversionComponent () {
- const { selectedTokenExchangeRate, showFiat, currentCurrency } = this.props
+ const { selectedTokenExchangeRate, showFiat, currentCurrency, hideConversion } = this.props
const { decimalValue } = this.state
let currency, numberOfDecimals
+ if (hideConversion) {
+ return (
+ <div className="currency-input__conversion-component">
+ { this.context.t('noConversionRateAvailable') }
+ </div>
+ )
+ }
+
if (showFiat) {
// Display Fiat
currency = currentCurrency
diff --git a/ui/app/components/token-input/token-input.container.js b/ui/app/components/ui/token-input/token-input.container.js
index ec233b1b8..981cb3598 100644
--- a/ui/app/components/token-input/token-input.container.js
+++ b/ui/app/components/ui/token-input/token-input.container.js
@@ -1,14 +1,17 @@
import { connect } from 'react-redux'
import TokenInput from './token-input.component'
-import { getSelectedToken, getSelectedTokenExchangeRate } from '../../selectors'
+import {getIsMainnet, getSelectedToken, getSelectedTokenExchangeRate, preferencesSelector} from '../../../selectors/selectors'
const mapStateToProps = state => {
const { metamask: { currentCurrency } } = state
+ const { showFiatInTestnets } = preferencesSelector(state)
+ const isMainnet = getIsMainnet(state)
return {
currentCurrency,
selectedToken: getSelectedToken(state),
selectedTokenExchangeRate: getSelectedTokenExchangeRate(state),
+ hideConversion: (!isMainnet && !showFiatInTestnets),
}
}
diff --git a/ui/app/components/tooltip-v2.js b/ui/app/components/ui/tooltip-v2.js
index 054782203..b54026794 100644
--- a/ui/app/components/tooltip-v2.js
+++ b/ui/app/components/ui/tooltip-v2.js
@@ -20,6 +20,7 @@ export default class Tooltip extends PureComponent {
arrow: PropTypes.bool,
children: PropTypes.node,
containerClassName: PropTypes.string,
+ disabled: PropTypes.bool,
onHidden: PropTypes.func,
position: PropTypes.oneOf([
'top',
@@ -33,10 +34,11 @@ export default class Tooltip extends PureComponent {
title: PropTypes.string,
trigger: PropTypes.any,
wrapperClassName: PropTypes.string,
+ style: PropTypes.object,
}
render () {
- const {arrow, children, containerClassName, position, size, title, trigger, onHidden, wrapperClassName } = this.props
+ const {arrow, children, containerClassName, disabled, position, size, title, trigger, onHidden, wrapperClassName, style } = this.props
if (!title) {
return (
@@ -50,6 +52,7 @@ export default class Tooltip extends PureComponent {
<div className={wrapperClassName}>
<ReactTippy
className={containerClassName}
+ disabled={disabled}
title={title}
position={position}
trigger={trigger}
@@ -57,6 +60,7 @@ export default class Tooltip extends PureComponent {
size={size}
arrow={arrow}
onHidden={onHidden}
+ style={style}
>
{children}
</ReactTippy>
diff --git a/ui/app/components/tooltip.js b/ui/app/components/ui/tooltip.js
index efab2c497..efab2c497 100644
--- a/ui/app/components/tooltip.js
+++ b/ui/app/components/ui/tooltip.js
diff --git a/ui/app/components/unit-input/index.js b/ui/app/components/ui/unit-input/index.js
index 7c33c9e5c..7c33c9e5c 100644
--- a/ui/app/components/unit-input/index.js
+++ b/ui/app/components/ui/unit-input/index.js
diff --git a/ui/app/components/unit-input/index.scss b/ui/app/components/ui/unit-input/index.scss
index 28c5bf6f0..e4075d225 100644
--- a/ui/app/components/unit-input/index.scss
+++ b/ui/app/components/ui/unit-input/index.scss
@@ -1,4 +1,7 @@
.unit-input {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
min-height: 54px;
border: 1px solid #dedede;
border-radius: 4px;
@@ -24,6 +27,10 @@
display: none;
}
+ &__inputs {
+ flex: 1 0 auto;
+ }
+
&__input {
color: #4d4d4d;
font-size: 1rem;
@@ -38,6 +45,10 @@
align-items: center;
}
+ &__suffix {
+ margin-left: 3px;
+ }
+
&--error {
border-color: $red;
}
diff --git a/ui/app/components/unit-input/tests/unit-input.component.test.js b/ui/app/components/ui/unit-input/tests/unit-input.component.test.js
index 97d987bc7..97d987bc7 100644
--- a/ui/app/components/unit-input/tests/unit-input.component.test.js
+++ b/ui/app/components/ui/unit-input/tests/unit-input.component.test.js
diff --git a/ui/app/components/unit-input/unit-input.component.js b/ui/app/components/ui/unit-input/unit-input.component.js
index f1ebf4d77..7b414f177 100644
--- a/ui/app/components/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 '../send/send.utils'
+import { removeLeadingZeroes } from '../../app/send/send.utils'
/**
* Component that attaches a suffix or unit of measurement trailing user input, ex. 'ETH'. Also
@@ -11,6 +11,7 @@ import { removeLeadingZeroes } from '../send/send.utils'
export default class UnitInput extends PureComponent {
static propTypes = {
children: PropTypes.node,
+ actionComponent: PropTypes.node,
error: PropTypes.bool,
onBlur: PropTypes.func,
onChange: PropTypes.func,
@@ -66,11 +67,11 @@ export default class UnitInput extends PureComponent {
const valueString = String(value)
const valueLength = valueString.length || 1
const decimalPointDeficit = valueString.match(/\./) ? -0.5 : 0
- return (valueLength + decimalPointDeficit + 0.75) + 'ch'
+ return (valueLength + decimalPointDeficit + 0.5) + 'ch'
}
render () {
- const { error, placeholder, suffix, children } = this.props
+ const { error, placeholder, suffix, actionComponent, children } = this.props
const { value } = this.state
return (
@@ -78,26 +79,29 @@ export default class UnitInput extends PureComponent {
className={classnames('unit-input', { 'unit-input--error': error })}
onClick={this.handleFocus}
>
- <div className="unit-input__input-container">
- <input
- type="number"
- className="unit-input__input"
- value={value}
- placeholder={placeholder}
- onChange={this.handleChange}
- onBlur={this.handleBlur}
- style={{ width: this.getInputWidth(value) }}
- ref={ref => { this.unitInput = ref }}
- />
- {
- suffix && (
- <div className="unit-input__suffix">
- { suffix }
- </div>
- )
- }
+ <div className="unit-input__inputs">
+ <div className="unit-input__input-container">
+ <input
+ type="number"
+ className="unit-input__input"
+ value={value}
+ placeholder={placeholder}
+ onChange={this.handleChange}
+ onBlur={this.handleBlur}
+ style={{ width: this.getInputWidth(value) }}
+ ref={ref => { this.unitInput = ref }}
+ />
+ {
+ suffix && (
+ <div className="unit-input__suffix">
+ { suffix }
+ </div>
+ )
+ }
+ </div>
+ { children }
</div>
- { children }
+ {actionComponent}
</div>
)
}