aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app')
-rw-r--r--ui/app/app.js14
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js9
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.js1
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/index.js1
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/index.scss2
-rw-r--r--ui/app/components/dropdowns/tests/dropdown.test.js37
-rw-r--r--ui/app/components/dropdowns/tests/menu.test.js87
-rw-r--r--ui/app/components/dropdowns/tests/network-dropdown-icon.test.js25
-rw-r--r--ui/app/components/dropdowns/tests/network-dropdown.test.js97
-rw-r--r--ui/app/components/error-message/error-message.component.js (renamed from ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js)14
-rw-r--r--ui/app/components/error-message/index.js1
-rw-r--r--ui/app/components/error-message/index.scss (renamed from ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.scss)2
-rw-r--r--ui/app/components/error-message/tests/error-message.component.test.js36
-rw-r--r--ui/app/components/index.scss4
-rw-r--r--ui/app/components/modal/index.js2
-rw-r--r--ui/app/components/modal/index.scss62
-rw-r--r--ui/app/components/modal/modal-content/index.js1
-rw-r--r--ui/app/components/modal/modal-content/index.scss19
-rw-r--r--ui/app/components/modal/modal-content/modal-content.component.js32
-rw-r--r--ui/app/components/modal/modal-content/tests/modal-content.component.test.js44
-rw-r--r--ui/app/components/modal/modal.component.js80
-rw-r--r--ui/app/components/modal/tests/modal.component.test.js103
-rw-r--r--ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js29
-rw-r--r--ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.js1
-rw-r--r--ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.scss17
-rw-r--r--ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js27
-rw-r--r--ui/app/components/modals/cancel-transaction/cancel-transaction.component.js68
-rw-r--r--ui/app/components/modals/cancel-transaction/cancel-transaction.container.js62
-rw-r--r--ui/app/components/modals/cancel-transaction/index.js1
-rw-r--r--ui/app/components/modals/cancel-transaction/index.scss18
-rw-r--r--ui/app/components/modals/cancel-transaction/tests/cancel-transaction.component.test.js56
-rw-r--r--ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js78
-rw-r--r--ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js12
-rw-r--r--ui/app/components/modals/confirm-remove-account/index.js3
-rw-r--r--ui/app/components/modals/confirm-remove-account/index.scss58
-rw-r--r--ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js48
-rw-r--r--ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js11
-rw-r--r--ui/app/components/modals/confirm-reset-account/index.js3
-rw-r--r--ui/app/components/modals/index.scss109
-rw-r--r--ui/app/components/modals/modal.js54
-rw-r--r--ui/app/components/modals/notification/index.js2
-rw-r--r--ui/app/components/modals/notification/notification.component.js30
-rw-r--r--ui/app/components/modals/notification/notification.container.js38
-rw-r--r--ui/app/components/modals/transaction-confirmed/index.js3
-rw-r--r--ui/app/components/modals/transaction-confirmed/index.scss22
-rw-r--r--ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js59
-rw-r--r--ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js4
-rw-r--r--ui/app/components/modals/transaction-details/index.js1
-rw-r--r--ui/app/components/modals/transaction-details/transaction-details.component.js54
-rw-r--r--ui/app/components/modals/transaction-details/transaction-details.container.js4
-rw-r--r--ui/app/components/modals/welcome-beta/index.js3
-rw-r--r--ui/app/components/modals/welcome-beta/welcome-beta.component.js23
-rw-r--r--ui/app/components/modals/welcome-beta/welcome-beta.container.js4
-rw-r--r--ui/app/components/page-container/index.scss2
-rw-r--r--ui/app/components/page-container/page-container-footer/tests/page-container-footer.component.test.js69
-rw-r--r--ui/app/components/page-container/page-container-header/tests/page-container-header.component.test.js82
-rw-r--r--ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js5
-rw-r--r--ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js4
-rw-r--r--ui/app/components/pages/index.scss2
-rw-r--r--ui/app/components/pages/settings/index.js65
-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/info.js120
-rw-r--r--ui/app/components/pages/settings/settings-tab/index.js1
-rw-r--r--ui/app/components/pages/settings/settings-tab/index.scss51
-rw-r--r--ui/app/components/pages/settings/settings-tab/settings-tab.component.js359
-rw-r--r--ui/app/components/pages/settings/settings-tab/settings-tab.container.js59
-rw-r--r--ui/app/components/pages/settings/settings.component.js54
-rw-r--r--ui/app/components/pages/settings/settings.js408
-rw-r--r--ui/app/components/pages/unlock-page/index.scss1
-rw-r--r--ui/app/components/send/currency-display/tests/currency-display.test.js91
-rw-r--r--ui/app/components/signature-request.js30
-rw-r--r--ui/app/components/transaction-action/tests/transaction-action.component.test.js9
-rw-r--r--ui/app/components/transaction-action/transaction-action.component.js4
-rw-r--r--ui/app/components/transaction-activity-log/index.scss13
-rw-r--r--ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js22
-rw-r--r--ui/app/components/transaction-list-item/index.scss11
-rw-r--r--ui/app/components/transaction-list-item/transaction-list-item.component.js81
-rw-r--r--ui/app/components/transaction-list-item/transaction-list-item.container.js15
-rw-r--r--ui/app/components/transaction-list/index.scss4
-rw-r--r--ui/app/components/transaction-list/transaction-list.component.js5
-rw-r--r--ui/app/conf-tx.js2
-rw-r--r--ui/app/constants/transactions.js1
-rw-r--r--ui/app/css/itcss/components/index.scss2
-rw-r--r--ui/app/css/itcss/components/loading-overlay.scss20
-rw-r--r--ui/app/css/itcss/components/newui-sections.scss10
-rw-r--r--ui/app/css/itcss/components/request-signature.scss19
-rw-r--r--ui/app/css/itcss/components/settings.scss214
-rw-r--r--ui/app/helpers/conversions.util.js12
-rw-r--r--ui/app/helpers/tests/transactions.util.test.js22
-rw-r--r--ui/app/helpers/transactions.util.js11
-rw-r--r--ui/app/higher-order-components/with-modal-props/index.js1
-rw-r--r--ui/app/higher-order-components/with-modal-props/tests/with-modal-props.test.js43
-rw-r--r--ui/app/higher-order-components/with-modal-props/with-modal-props.js21
96 files changed, 2581 insertions, 1212 deletions
diff --git a/ui/app/app.js b/ui/app/app.js
index c93f93e75..aeb3d05ee 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -19,9 +19,9 @@ const Sidebar = require('./components/sidebars').default
// other views
import Home from './components/pages/home'
+import Settings from './components/pages/settings'
const Authenticated = require('./components/pages/authenticated')
const Initialized = require('./components/pages/initialized')
-const Settings = require('./components/pages/settings')
const RestoreVaultPage = require('./components/pages/keychains/restore-vault').default
const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed')
const AddTokenPage = require('./components/pages/add-token')
@@ -152,12 +152,14 @@ class App extends Component {
h(AccountMenu),
- (isLoading || isLoadingNetwork) && h(Loading, {
- loadingMessage: loadMessage,
- }),
+ h('div.main-container-wrapper', [
+ (isLoading || isLoadingNetwork) && h(Loading, {
+ loadingMessage: loadMessage,
+ }),
- // content
- this.renderRoutes(),
+ // content
+ this.renderRoutes(),
+ ]),
])
)
}
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
index de9aa6eb7..74e95ece6 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
@@ -2,11 +2,8 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { Tabs, Tab } from '../../tabs'
-import {
- ConfirmPageContainerSummary,
- ConfirmPageContainerError,
- ConfirmPageContainerWarning,
-} from './'
+import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from './'
+import ErrorMessage from '../../error-message'
export default class ConfirmPageContainerContent extends Component {
static propTypes = {
@@ -95,7 +92,7 @@ export default class ConfirmPageContainerContent extends Component {
{
(errorKey || errorMessage) && (
<div className="confirm-page-container-content__error-container">
- <ConfirmPageContainerError
+ <ErrorMessage
errorMessage={errorMessage}
errorKey={errorKey}
/>
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.js b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.js
deleted file mode 100644
index 4ac95d0e3..000000000
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './confirm-page-container-error.component'
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/index.js b/ui/app/components/confirm-page-container/confirm-page-container-content/index.js
index 1469dd438..4dfd89d92 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/index.js
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/index.js
@@ -1,4 +1,3 @@
export { default } from './confirm-page-container-content.component'
export { default as ConfirmPageContainerSummary } from './confirm-page-container-summary'
-export { default as ConfirmPageContainerError } from './confirm-page-container-error'
export { default as ConfirmPageContainerWarning } from './confirm-page-container-warning'
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss b/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss
index 39797a43f..698e624f4 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss
@@ -1,5 +1,3 @@
-@import './confirm-page-container-error/index';
-
@import './confirm-page-container-warning/index';
@import './confirm-page-container-summary/index';
diff --git a/ui/app/components/dropdowns/tests/dropdown.test.js b/ui/app/components/dropdowns/tests/dropdown.test.js
new file mode 100644
index 000000000..2b026589a
--- /dev/null
+++ b/ui/app/components/dropdowns/tests/dropdown.test.js
@@ -0,0 +1,37 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { shallow } from 'enzyme'
+import { DropdownMenuItem } from '../components/dropdown.js'
+
+describe('', () => {
+ let wrapper
+ const onClickSpy = sinon.spy()
+ const closeMenuSpy = sinon.spy()
+
+ beforeEach(() => {
+ wrapper = shallow(
+ <DropdownMenuItem
+ onClick = {onClickSpy}
+ style = {{test: 'style'}}
+ closeMenu = {closeMenuSpy}
+ >
+ </DropdownMenuItem>
+ )
+ })
+
+ it('renders li with dropdown-menu-item class', () => {
+ assert.equal(wrapper.find('li.dropdown-menu-item').length, 1)
+ })
+
+ it('adds style based on props passed', () => {
+ assert.equal(wrapper.prop('style').test, 'style')
+ })
+
+ it('simulates click event and calls onClick and closeMenu', () => {
+ wrapper.prop('onClick')()
+ assert.equal(onClickSpy.callCount, 1)
+ assert.equal(closeMenuSpy.callCount, 1)
+ })
+
+})
diff --git a/ui/app/components/dropdowns/tests/menu.test.js b/ui/app/components/dropdowns/tests/menu.test.js
new file mode 100644
index 000000000..9f5f13f00
--- /dev/null
+++ b/ui/app/components/dropdowns/tests/menu.test.js
@@ -0,0 +1,87 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { shallow } from 'enzyme'
+import { Menu, Item, Divider, CloseArea } from '../components/menu'
+
+describe('Dropdown Menu Components', () => {
+
+ describe('Menu', () => {
+ let wrapper
+
+ beforeEach(() => {
+ wrapper = shallow(
+ <Menu className = {'Test Class'} isShowing = {true}/>
+ )
+ })
+
+ it('adds prop className to menu', () => {
+ assert.equal(wrapper.find('.menu').prop('className'), 'menu Test Class')
+ })
+
+ })
+
+ describe('Item', () => {
+ let wrapper
+
+ const onClickSpy = sinon.spy()
+
+ beforeEach(() => {
+ wrapper = shallow(
+ <Item
+ icon = {'test icon'}
+ text = {'test text'}
+ className = {'test className'}
+ onClick = {onClickSpy}
+ />
+ )
+ })
+
+ it('add className based on props', () => {
+ assert.equal(wrapper.find('.menu__item').prop('className'), 'menu__item menu__item test className menu__item--clickable')
+ })
+
+ it('simulates onClick called', () => {
+ wrapper.find('.menu__item').prop('onClick')()
+ assert.equal(onClickSpy.callCount, 1)
+ })
+
+ it('adds icon based on icon props', () => {
+ assert.equal(wrapper.find('.menu__item__icon').text(), 'test icon')
+ })
+
+ it('adds html text based on text props', () => {
+ assert.equal(wrapper.find('.menu__item__text').text(), 'test text')
+ })
+ })
+
+ describe('Divider', () => {
+ let wrapper
+
+ before(() => {
+ wrapper = shallow(<Divider />)
+ })
+
+ it('renders menu divider', () => {
+ assert.equal(wrapper.find('.menu__divider').length, 1)
+ })
+ })
+
+ describe('CloseArea', () => {
+ let wrapper
+
+ const onClickSpy = sinon.spy()
+
+ beforeEach(() => {
+ wrapper = shallow(<CloseArea
+ onClick = {onClickSpy}
+ />)
+ })
+
+ it('simulates click', () => {
+ wrapper.prop('onClick')()
+ assert.equal(onClickSpy.callCount, 1)
+ })
+ })
+
+})
diff --git a/ui/app/components/dropdowns/tests/network-dropdown-icon.test.js b/ui/app/components/dropdowns/tests/network-dropdown-icon.test.js
new file mode 100644
index 000000000..67b192c11
--- /dev/null
+++ b/ui/app/components/dropdowns/tests/network-dropdown-icon.test.js
@@ -0,0 +1,25 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import NetworkDropdownIcon from '../components/network-dropdown-icon'
+
+describe('Network Dropdown Icon', () => {
+ let wrapper
+
+ beforeEach(() => {
+ wrapper = shallow(<NetworkDropdownIcon
+ backgroundColor = {'red'}
+ isSelected = {false}
+ innerBorder = {'none'}
+ diameter = {'12'}
+ />)
+ })
+
+ it('adds style props based on props', () => {
+ const styleProp = wrapper.find('.menu-icon-circle').children().prop('style')
+ assert.equal(styleProp.background, 'red')
+ assert.equal(styleProp.border, 'none')
+ assert.equal(styleProp.height, '12px')
+ assert.equal(styleProp.width, '12px')
+ })
+})
diff --git a/ui/app/components/dropdowns/tests/network-dropdown.test.js b/ui/app/components/dropdowns/tests/network-dropdown.test.js
new file mode 100644
index 000000000..699b54605
--- /dev/null
+++ b/ui/app/components/dropdowns/tests/network-dropdown.test.js
@@ -0,0 +1,97 @@
+import React from 'react'
+import assert from 'assert'
+import { createMockStore } from 'redux-test-utils'
+import { mountWithRouter } from '../../../../../test/lib/render-helpers'
+import NetworkDropdown from '../network-dropdown'
+import { DropdownMenuItem } from '../components/dropdown'
+import NetworkDropdownIcon from '../components/network-dropdown-icon'
+
+describe('Network Dropdown', () => {
+ let wrapper
+
+ describe('NetworkDropdown in appState in false', () => {
+ const mockState = {
+ metamask: {
+ provider: {
+ type: 'test',
+ },
+ },
+ appState: {
+ networkDropdown: false,
+ },
+ }
+
+ const store = createMockStore(mockState)
+
+ beforeEach(() => {
+ wrapper = mountWithRouter(
+ <NetworkDropdown store={store} />
+ )
+ })
+
+ it('checks for network droppo class', () => {
+ assert.equal(wrapper.find('.network-droppo').length, 1)
+ })
+
+ it('renders only one child when networkDropdown is false in state', () => {
+ assert.equal(wrapper.children().length, 1)
+ })
+
+ })
+
+ describe('NetworkDropdown in appState is true', () => {
+ const mockState = {
+ metamask: {
+ provider: {
+ 'type': 'test',
+ },
+ frequentRpcList: [
+ 'http://localhost:7545',
+ ],
+ },
+ appState: {
+ 'networkDropdownOpen': true,
+ },
+ }
+ const store = createMockStore(mockState)
+
+ beforeEach(() => {
+ wrapper = mountWithRouter(
+ <NetworkDropdown store={store}/>,
+ )
+ })
+
+ it('renders 7 DropDownMenuItems ', () => {
+ assert.equal(wrapper.find(DropdownMenuItem).length, 7)
+ })
+
+ it('checks background color for first NetworkDropdownIcon', () => {
+ assert.equal(wrapper.find(NetworkDropdownIcon).at(0).prop('backgroundColor'), '#29B6AF') // Main Ethereum Network Teal
+ })
+
+ it('checks background color for second NetworkDropdownIcon', () => {
+ assert.equal(wrapper.find(NetworkDropdownIcon).at(1).prop('backgroundColor'), '#ff4a8d') // Ropsten Red
+ })
+
+ it('checks background color for third NetworkDropdownIcon', () => {
+ assert.equal(wrapper.find(NetworkDropdownIcon).at(2).prop('backgroundColor'), '#7057ff') // Kovan Purple
+ })
+
+ it('checks background color for fourth NetworkDropdownIcon', () => {
+ assert.equal(wrapper.find(NetworkDropdownIcon).at(3).prop('backgroundColor'), '#f6c343') // Rinkeby Yellow
+ })
+
+ it('checks background color for fifth NetworkDropdownIcon', () => {
+ assert.equal(wrapper.find(NetworkDropdownIcon).at(4).prop('innerBorder'), '1px solid #9b9b9b')
+ })
+
+ it('checks dropdown for frequestRPCList from state ', () => {
+ assert.equal(wrapper.find(DropdownMenuItem).at(5).text(), '✓http://localhost:7545')
+ })
+
+ it('checks background color for sixth NetworkDropdownIcon', () => {
+ assert.equal(wrapper.find(NetworkDropdownIcon).at(5).prop('innerBorder'), '1px solid #9b9b9b')
+ })
+
+ })
+})
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js b/ui/app/components/error-message/error-message.component.js
index 4965d7b4e..b4464c33b 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js
+++ b/ui/app/components/error-message/error-message.component.js
@@ -1,30 +1,30 @@
import React from 'react'
import PropTypes from 'prop-types'
-const ConfirmPageContainerError = (props, context) => {
+const ErrorMessage = (props, context) => {
const { errorMessage, errorKey } = props
const error = errorKey ? context.t(errorKey) : errorMessage
return (
- <div className="confirm-page-container-error">
+ <div className="error-message">
<img
src="/images/alert-red.svg"
- className="confirm-page-container-error__icon"
+ className="error-message__icon"
/>
- <div className="confirm-page-container-error__text">
+ <div className="error-message__text">
{ `ALERT: ${error}` }
</div>
</div>
)
}
-ConfirmPageContainerError.propTypes = {
+ErrorMessage.propTypes = {
errorMessage: PropTypes.string,
errorKey: PropTypes.string,
}
-ConfirmPageContainerError.contextTypes = {
+ErrorMessage.contextTypes = {
t: PropTypes.func,
}
-export default ConfirmPageContainerError
+export default ErrorMessage
diff --git a/ui/app/components/error-message/index.js b/ui/app/components/error-message/index.js
new file mode 100644
index 000000000..1c97a9955
--- /dev/null
+++ b/ui/app/components/error-message/index.js
@@ -0,0 +1 @@
+export { default } from './error-message.component'
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.scss b/ui/app/components/error-message/index.scss
index 89ff25578..5915e21cf 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.scss
+++ b/ui/app/components/error-message/index.scss
@@ -1,4 +1,4 @@
-.confirm-page-container-error {
+.error-message {
min-height: 32px;
border: 1px solid $monzo;
color: $monzo;
diff --git a/ui/app/components/error-message/tests/error-message.component.test.js b/ui/app/components/error-message/tests/error-message.component.test.js
new file mode 100644
index 000000000..8c5347173
--- /dev/null
+++ b/ui/app/components/error-message/tests/error-message.component.test.js
@@ -0,0 +1,36 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import ErrorMessage from '../error-message.component'
+
+describe('ErrorMessage Component', () => {
+ const t = key => `translate ${key}`
+
+ it('should render a message from props.errorMessage', () => {
+ const wrapper = shallow(
+ <ErrorMessage
+ errorMessage="This is an error."
+ />,
+ { context: { t }}
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find('.error-message').length, 1)
+ assert.equal(wrapper.find('.error-message__icon').length, 1)
+ assert.equal(wrapper.find('.error-message__text').text(), 'ALERT: This is an error.')
+ })
+
+ it('should render a message translated from props.errorKey', () => {
+ const wrapper = shallow(
+ <ErrorMessage
+ errorKey="testKey"
+ />,
+ { context: { t }}
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find('.error-message').length, 1)
+ assert.equal(wrapper.find('.error-message__icon').length, 1)
+ assert.equal(wrapper.find('.error-message__text').text(), 'ALERT: translate testKey')
+ })
+})
diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss
index 983d6b98a..21b65bf55 100644
--- a/ui/app/components/index.scss
+++ b/ui/app/components/index.scss
@@ -6,12 +6,16 @@
@import './confirm-page-container/index';
+@import './error-message/index';
+
@import './export-text-container/index';
@import './info-box/index';
@import './menu-bar/index';
+@import './modal/index';
+
@import './modals/index';
@import './network-display/index';
diff --git a/ui/app/components/modal/index.js b/ui/app/components/modal/index.js
new file mode 100644
index 000000000..58309abbe
--- /dev/null
+++ b/ui/app/components/modal/index.js
@@ -0,0 +1,2 @@
+export { default } from './modal.component'
+export { default as ModalContent } from './modal-content'
diff --git a/ui/app/components/modal/index.scss b/ui/app/components/modal/index.scss
new file mode 100644
index 000000000..2beb14633
--- /dev/null
+++ b/ui/app/components/modal/index.scss
@@ -0,0 +1,62 @@
+@import './modal-content/index';
+
+.modal-container {
+ width: 100%;
+ height: 100%;
+ background-color: #fff;
+ display: flex;
+ flex-flow: column;
+ border-radius: 8px;
+
+ @media screen and (max-width: 575px) {
+ max-height: 450px;
+ }
+
+ &__content {
+ overflow-y: auto;
+ flex: 1;
+ padding: 16px 32px;
+
+ @media screen and (max-width: 575px) {
+ justify-content: center;
+ padding: 28px 20px;
+ }
+ }
+
+ &__header {
+ position: relative;
+ display: flex;
+ padding: 12px;
+ justify-content: center;
+ border-bottom: 1px solid #d2d8dd;
+ flex: 0 0 auto;
+ }
+
+ &__header-close::after {
+ content: '\00D7';
+ font-size: 40px;
+ color: $dusty-gray;
+ position: absolute;
+ top: -5px;
+ right: 10px;
+ cursor: pointer;
+ }
+
+ &__footer {
+ display: flex;
+ flex-flow: row;
+ justify-content: center;
+ border-top: 1px solid #d2d8dd;
+ padding: 16px;
+ flex: 0 0 auto;
+
+ &-button {
+ min-width: 0;
+ margin-right: 16px;
+
+ &:last-of-type {
+ margin-right: 0;
+ }
+ }
+ }
+}
diff --git a/ui/app/components/modal/modal-content/index.js b/ui/app/components/modal/modal-content/index.js
new file mode 100644
index 000000000..733cfb3b8
--- /dev/null
+++ b/ui/app/components/modal/modal-content/index.js
@@ -0,0 +1 @@
+export { default } from './modal-content.component'
diff --git a/ui/app/components/modal/modal-content/index.scss b/ui/app/components/modal/modal-content/index.scss
new file mode 100644
index 000000000..560505b84
--- /dev/null
+++ b/ui/app/components/modal/modal-content/index.scss
@@ -0,0 +1,19 @@
+.modal-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 16px 0;
+
+ &__title {
+ font-size: 1.5rem;
+ font-weight: 500;
+ padding: 16px 0;
+ text-align: center;
+ }
+
+ &__description {
+ text-align: center;
+ font-size: .875rem;
+ }
+}
diff --git a/ui/app/components/modal/modal-content/modal-content.component.js b/ui/app/components/modal/modal-content/modal-content.component.js
new file mode 100644
index 000000000..ecec0ee5b
--- /dev/null
+++ b/ui/app/components/modal/modal-content/modal-content.component.js
@@ -0,0 +1,32 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+
+export default class ModalContent extends PureComponent {
+ static propTypes = {
+ title: PropTypes.string,
+ description: PropTypes.string,
+ }
+
+ render () {
+ const { title, description } = this.props
+
+ return (
+ <div className="modal-content">
+ {
+ title && (
+ <div className="modal-content__title">
+ { title }
+ </div>
+ )
+ }
+ {
+ description && (
+ <div className="modal-content__description">
+ { description }
+ </div>
+ )
+ }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/modal/modal-content/tests/modal-content.component.test.js b/ui/app/components/modal/modal-content/tests/modal-content.component.test.js
new file mode 100644
index 000000000..17af09f45
--- /dev/null
+++ b/ui/app/components/modal/modal-content/tests/modal-content.component.test.js
@@ -0,0 +1,44 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import ModalContent from '../modal-content.component'
+
+describe('ModalContent Component', () => {
+ it('should render a title', () => {
+ const wrapper = shallow(
+ <ModalContent
+ title="Modal Title"
+ />
+ )
+
+ assert.equal(wrapper.find('.modal-content__title').length, 1)
+ assert.equal(wrapper.find('.modal-content__title').text(), 'Modal Title')
+ assert.equal(wrapper.find('.modal-content__description').length, 0)
+ })
+
+ it('should render a description', () => {
+ const wrapper = shallow(
+ <ModalContent
+ description="Modal Description"
+ />
+ )
+
+ assert.equal(wrapper.find('.modal-content__title').length, 0)
+ assert.equal(wrapper.find('.modal-content__description').length, 1)
+ assert.equal(wrapper.find('.modal-content__description').text(), 'Modal Description')
+ })
+
+ it('should render both a title and a description', () => {
+ const wrapper = shallow(
+ <ModalContent
+ title="Modal Title"
+ description="Modal Description"
+ />
+ )
+
+ assert.equal(wrapper.find('.modal-content__title').length, 1)
+ assert.equal(wrapper.find('.modal-content__title').text(), 'Modal Title')
+ assert.equal(wrapper.find('.modal-content__description').length, 1)
+ assert.equal(wrapper.find('.modal-content__description').text(), 'Modal Description')
+ })
+})
diff --git a/ui/app/components/modal/modal.component.js b/ui/app/components/modal/modal.component.js
new file mode 100644
index 000000000..2a75b559b
--- /dev/null
+++ b/ui/app/components/modal/modal.component.js
@@ -0,0 +1,80 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Button from '../button'
+
+export default class Modal extends PureComponent {
+ static propTypes = {
+ children: PropTypes.node,
+ // Header text
+ headerText: PropTypes.string,
+ onClose: PropTypes.func,
+ // Submit button (right button)
+ onSubmit: PropTypes.func,
+ submitType: PropTypes.string,
+ submitText: PropTypes.string,
+ // Cancel button (left button)
+ onCancel: PropTypes.func,
+ cancelType: PropTypes.string,
+ cancelText: PropTypes.string,
+ }
+
+ static defaultProps = {
+ submitType: 'primary',
+ cancelType: 'default',
+ }
+
+ render () {
+ const {
+ children,
+ headerText,
+ onClose,
+ onSubmit,
+ submitType,
+ submitText,
+ onCancel,
+ cancelType,
+ cancelText,
+ } = this.props
+
+ return (
+ <div className="modal-container">
+ {
+ headerText && (
+ <div className="modal-container__header">
+ <div className="modal-container__header-text">
+ { headerText }
+ </div>
+ <div
+ className="modal-container__header-close"
+ onClick={onClose}
+ />
+ </div>
+ )
+ }
+ <div className="modal-container__content">
+ { children }
+ </div>
+ <div className="modal-container__footer">
+ {
+ onCancel && (
+ <Button
+ type={cancelType}
+ onClick={onCancel}
+ className="modal-container__footer-button"
+ >
+ { cancelText }
+ </Button>
+ )
+ }
+ <Button
+ type={submitType}
+ onClick={onSubmit}
+ className="modal-container__footer-button"
+ >
+ { submitText }
+ </Button>
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/modal/tests/modal.component.test.js b/ui/app/components/modal/tests/modal.component.test.js
new file mode 100644
index 000000000..8cce1a808
--- /dev/null
+++ b/ui/app/components/modal/tests/modal.component.test.js
@@ -0,0 +1,103 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import sinon from 'sinon'
+import Modal from '../modal.component'
+import Button from '../../button'
+
+describe('Modal Component', () => {
+ it('should render a modal with a submit button', () => {
+ const wrapper = shallow(<Modal />)
+
+ assert.equal(wrapper.find('.modal-container').length, 1)
+ const buttons = wrapper.find(Button)
+ assert.equal(buttons.length, 1)
+ assert.equal(buttons.at(0).props().type, 'primary')
+ })
+
+ it('should render a modal with a cancel and a submit button', () => {
+ const handleCancel = sinon.spy()
+ const handleSubmit = sinon.spy()
+ const wrapper = shallow(
+ <Modal
+ onCancel={handleCancel}
+ cancelText="Cancel"
+ onSubmit={handleSubmit}
+ submitText="Submit"
+ />
+ )
+
+ const buttons = wrapper.find(Button)
+ assert.equal(buttons.length, 2)
+ const cancelButton = buttons.at(0)
+ const submitButton = buttons.at(1)
+
+ assert.equal(cancelButton.props().type, 'default')
+ assert.equal(cancelButton.props().children, 'Cancel')
+ assert.equal(handleCancel.callCount, 0)
+ cancelButton.simulate('click')
+ assert.equal(handleCancel.callCount, 1)
+
+ assert.equal(submitButton.props().type, 'primary')
+ assert.equal(submitButton.props().children, 'Submit')
+ assert.equal(handleSubmit.callCount, 0)
+ submitButton.simulate('click')
+ assert.equal(handleSubmit.callCount, 1)
+ })
+
+ it('should render a modal with different button types', () => {
+ const wrapper = shallow(
+ <Modal
+ onCancel={() => {}}
+ cancelText="Cancel"
+ cancelType="secondary"
+ onSubmit={() => {}}
+ submitText="Submit"
+ submitType="confirm"
+ />
+ )
+
+ const buttons = wrapper.find(Button)
+ assert.equal(buttons.length, 2)
+ assert.equal(buttons.at(0).props().type, 'secondary')
+ assert.equal(buttons.at(1).props().type, 'confirm')
+ })
+
+ it('should render a modal with children', () => {
+ const wrapper = shallow(
+ <Modal
+ onCancel={() => {}}
+ cancelText="Cancel"
+ onSubmit={() => {}}
+ submitText="Submit"
+ >
+ <div className="test-child" />
+ </Modal>
+ )
+
+ assert.ok(wrapper.find('.test-class'))
+ })
+
+ it('should render a modal with a header', () => {
+ const handleCancel = sinon.spy()
+ const handleSubmit = sinon.spy()
+ const wrapper = shallow(
+ <Modal
+ onCancel={handleCancel}
+ cancelText="Cancel"
+ onSubmit={handleSubmit}
+ submitText="Submit"
+ headerText="My Header"
+ onClose={handleCancel}
+ />
+ )
+
+ assert.ok(wrapper.find('.modal-container__header'))
+ assert.equal(wrapper.find('.modal-container__header-text').text(), 'My Header')
+ assert.equal(handleCancel.callCount, 0)
+ assert.equal(handleSubmit.callCount, 0)
+ wrapper.find('.modal-container__header-close').simulate('click')
+ assert.equal(handleCancel.callCount, 1)
+ assert.equal(handleSubmit.callCount, 0)
+ })
+})
diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js
new file mode 100644
index 000000000..b082db1d0
--- /dev/null
+++ b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js
@@ -0,0 +1,29 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import CurrencyDisplay from '../../../currency-display'
+import { ETH } from '../../../../constants/common'
+
+export default class CancelTransaction extends PureComponent {
+ static propTypes = {
+ value: PropTypes.string,
+ }
+
+ render () {
+ const { value } = this.props
+
+ return (
+ <div className="cancel-transaction-gas-fee">
+ <CurrencyDisplay
+ className="cancel-transaction-gas-fee__eth"
+ currency={ETH}
+ value={value}
+ numberOfDecimals={6}
+ />
+ <CurrencyDisplay
+ className="cancel-transaction-gas-fee__fiat"
+ value={value}
+ />
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.js b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.js
new file mode 100644
index 000000000..1a9ae2e07
--- /dev/null
+++ b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.js
@@ -0,0 +1 @@
+export { default } from './cancel-transaction-gas-fee.component'
diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.scss b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.scss
new file mode 100644
index 000000000..ce81dd448
--- /dev/null
+++ b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.scss
@@ -0,0 +1,17 @@
+.cancel-transaction-gas-fee {
+ background: #F1F4F9;
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 12px;
+
+ &__eth {
+ font-size: 1.5rem;
+ font-weight: 500;
+ }
+
+ &__fiat {
+ font-size: .75rem;
+ }
+}
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/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js
new file mode 100644
index 000000000..994c2a577
--- /dev/null
+++ b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js
@@ -0,0 +1,27 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import CancelTransactionGasFee from '../cancel-transaction-gas-fee.component'
+import CurrencyDisplay from '../../../../currency-display'
+
+describe('CancelTransactionGasFee Component', () => {
+ it('should render', () => {
+ const wrapper = shallow(
+ <CancelTransactionGasFee
+ value="0x3b9aca00"
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find(CurrencyDisplay).length, 2)
+ const ethDisplay = wrapper.find(CurrencyDisplay).at(0)
+ const fiatDisplay = wrapper.find(CurrencyDisplay).at(1)
+
+ assert.equal(ethDisplay.props().value, '0x3b9aca00')
+ assert.equal(ethDisplay.props().currency, 'ETH')
+ assert.equal(ethDisplay.props().className, 'cancel-transaction-gas-fee__eth')
+
+ assert.equal(fiatDisplay.props().value, '0x3b9aca00')
+ assert.equal(fiatDisplay.props().className, 'cancel-transaction-gas-fee__fiat')
+ })
+})
diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js b/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js
new file mode 100644
index 000000000..8b00cb9b9
--- /dev/null
+++ b/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js
@@ -0,0 +1,68 @@
+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'
+
+export default class CancelTransaction extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ createCancelTransaction: PropTypes.func,
+ hideModal: PropTypes.func,
+ showTransactionConfirmedModal: PropTypes.func,
+ transactionStatus: PropTypes.string,
+ newGasFee: PropTypes.string,
+ }
+
+ componentDidUpdate () {
+ const { transactionStatus, showTransactionConfirmedModal } = this.props
+
+ if (transactionStatus !== SUBMITTED_STATUS) {
+ showTransactionConfirmedModal()
+ return
+ }
+ }
+
+ handleSubmit = async () => {
+ const { createCancelTransaction, hideModal } = this.props
+
+ await createCancelTransaction()
+ hideModal()
+ }
+
+ handleCancel = () => {
+ this.props.hideModal()
+ }
+
+ render () {
+ const { t } = this.context
+ const { newGasFee } = this.props
+
+ return (
+ <Modal
+ headerText={t('attemptToCancel')}
+ onClose={this.handleCancel}
+ onSubmit={this.handleSubmit}
+ onCancel={this.handleCancel}
+ submitText={t('yesLetsTry')}
+ cancelText={t('nevermind')}
+ submitType="secondary"
+ >
+ <div>
+ <div className="cancel-transaction__title">
+ { t('cancellationGasFee') }
+ </div>
+ <div className="cancel-transaction__cancel-transaction-gas-fee-container">
+ <CancelTransactionGasFee value={newGasFee} />
+ </div>
+ <div className="cancel-transaction__description">
+ { t('attemptToCancelDescription') }
+ </div>
+ </div>
+ </Modal>
+ )
+ }
+}
diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction.container.js b/ui/app/components/modals/cancel-transaction/cancel-transaction.container.js
new file mode 100644
index 000000000..eede8b1ee
--- /dev/null
+++ b/ui/app/components/modals/cancel-transaction/cancel-transaction.container.js
@@ -0,0 +1,62 @@
+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 CancelTransaction from './cancel-transaction.component'
+import { showModal, createCancelTransaction } from '../../../actions'
+import { getHexGasTotal } from '../../../helpers/confirm-transaction/util'
+
+const mapStateToProps = (state, ownProps) => {
+ const { metamask } = state
+ const { transactionId, originalGasPrice } = ownProps
+ const { selectedAddressTxList } = metamask
+ const transaction = selectedAddressTxList.find(({ id }) => id === transactionId)
+ const transactionStatus = transaction ? transaction.status : ''
+
+ const defaultNewGasPrice = ethUtil.addHexPrefix(
+ multiplyCurrencies(originalGasPrice, 1.1, {
+ toNumericBase: 'hex',
+ multiplicandBase: 16,
+ multiplierBase: 10,
+ })
+ )
+
+ const newGasFee = getHexGasTotal({ gasPrice: defaultNewGasPrice, gasLimit: '0x5208' })
+
+ return {
+ transactionId,
+ transactionStatus,
+ originalGasPrice,
+ newGasFee,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ createCancelTransaction: txId => dispatch(createCancelTransaction(txId)),
+ showTransactionConfirmedModal: () => dispatch(showModal({ name: 'TRANSACTION_CONFIRMED' })),
+ }
+}
+
+const mergeProps = (stateProps, dispatchProps, ownProps) => {
+ const { transactionId, ...restStateProps } = stateProps
+ const {
+ createCancelTransaction: dispatchCreateCancelTransaction,
+ ...restDispatchProps
+ } = dispatchProps
+
+ return {
+ ...restStateProps,
+ ...restDispatchProps,
+ ...ownProps,
+ createCancelTransaction: newGasPrice => {
+ return dispatchCreateCancelTransaction(transactionId, newGasPrice)
+ },
+ }
+}
+
+export default compose(
+ withModalProps,
+ connect(mapStateToProps, mapDispatchToProps, mergeProps),
+)(CancelTransaction)
diff --git a/ui/app/components/modals/cancel-transaction/index.js b/ui/app/components/modals/cancel-transaction/index.js
new file mode 100644
index 000000000..7abc871ee
--- /dev/null
+++ b/ui/app/components/modals/cancel-transaction/index.js
@@ -0,0 +1 @@
+export { default } from './cancel-transaction.container'
diff --git a/ui/app/components/modals/cancel-transaction/index.scss b/ui/app/components/modals/cancel-transaction/index.scss
new file mode 100644
index 000000000..62e8e36fd
--- /dev/null
+++ b/ui/app/components/modals/cancel-transaction/index.scss
@@ -0,0 +1,18 @@
+@import './cancel-transaction-gas-fee/index';
+
+.cancel-transaction {
+ &__title {
+ font-weight: 500;
+ padding-bottom: 16px;
+ text-align: center;
+ }
+
+ &__description {
+ text-align: center;
+ font-size: .875rem;
+ }
+
+ &__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/modals/cancel-transaction/tests/cancel-transaction.component.test.js
new file mode 100644
index 000000000..858fb01a8
--- /dev/null
+++ b/ui/app/components/modals/cancel-transaction/tests/cancel-transaction.component.test.js
@@ -0,0 +1,56 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import sinon from 'sinon'
+import CancelTransaction from '../cancel-transaction.component'
+import CancelTransactionGasFee from '../cancel-transaction-gas-fee'
+import Modal from '../../../modal'
+
+describe('CancelTransaction Component', () => {
+ const t = key => key
+
+ it('should render a CancelTransaction modal', () => {
+ const wrapper = shallow(
+ <CancelTransaction
+ newGasFee="0x1319718a5000"
+ />,
+ { context: { t }}
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find(Modal).length, 1)
+ assert.equal(wrapper.find(CancelTransactionGasFee).length, 1)
+ assert.equal(wrapper.find(CancelTransactionGasFee).props().value, '0x1319718a5000')
+ assert.equal(wrapper.find('.cancel-transaction__title').text(), 'cancellationGasFee')
+ assert.equal(wrapper.find('.cancel-transaction__description').text(), 'attemptToCancelDescription')
+ })
+
+ it('should pass the correct props to the Modal component', async () => {
+ const createCancelTransactionSpy = sinon.stub().callsFake(() => Promise.resolve())
+ const hideModalSpy = sinon.spy()
+
+ const wrapper = shallow(
+ <CancelTransaction
+ defaultNewGasPrice="0x3b9aca00"
+ createCancelTransaction={createCancelTransactionSpy}
+ hideModal={hideModalSpy}
+ />,
+ { context: { t }}
+ )
+
+ assert.equal(wrapper.find(Modal).length, 1)
+ const modalProps = wrapper.find(Modal).props()
+
+ assert.equal(modalProps.headerText, 'attemptToCancel')
+ assert.equal(modalProps.submitText, 'yesLetsTry')
+ assert.equal(modalProps.cancelText, 'nevermind')
+
+ assert.equal(createCancelTransactionSpy.callCount, 0)
+ assert.equal(hideModalSpy.callCount, 0)
+ await modalProps.onSubmit()
+ assert.equal(createCancelTransactionSpy.callCount, 1)
+ assert.equal(hideModalSpy.callCount, 1)
+ modalProps.onCancel()
+ assert.equal(hideModalSpy.callCount, 2)
+ })
+})
diff --git a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js
index 5a9f0f289..eff94a54a 100644
--- a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js
+++ b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js
@@ -1,11 +1,11 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import Button from '../../button'
+import Modal from '../../modal'
import { addressSummary } from '../../../util'
import Identicon from '../../identicon'
import genAccountLink from '../../../../lib/account-link'
-class ConfirmRemoveAccount extends Component {
+export default class ConfirmRemoveAccount extends Component {
static propTypes = {
hideModal: PropTypes.func.isRequired,
removeAccount: PropTypes.func.isRequired,
@@ -17,30 +17,34 @@ class ConfirmRemoveAccount extends Component {
t: PropTypes.func,
}
- handleRemove () {
+ handleRemove = () => {
this.props.removeAccount(this.props.identity.address)
.then(() => this.props.hideModal())
}
+ handleCancel = () => {
+ this.props.hideModal()
+ }
+
renderSelectedAccount () {
const { identity } = this.props
return (
- <div className="modal-container__account">
- <div className="modal-container__account__identicon">
+ <div className="confirm-remove-account__account">
+ <div className="confirm-remove-account__account__identicon">
<Identicon
- address={identity.address}
- diameter={32}
+ address={identity.address}
+ diameter={32}
/>
</div>
- <div className="modal-container__account__name">
- <span className="modal-container__account__label">Name</span>
- <span className="account_value">{identity.name}</span>
+ <div className="confirm-remove-account__account__name">
+ <span className="confirm-remove-account__account__label">Name</span>
+ <span className="account_value">{identity.name}</span>
</div>
- <div className="modal-container__account__address">
- <span className="modal-container__account__label">Public Address</span>
- <span className="account_value">{ addressSummary(identity.address, 4, 4) }</span>
+ <div className="confirm-remove-account__account__address">
+ <span className="confirm-remove-account__account__label">Public Address</span>
+ <span className="account_value">{ addressSummary(identity.address, 4, 4) }</span>
</div>
- <div className="modal-container__account__link">
+ <div className="confirm-remove-account__account__link">
<a
className=""
href={genAccountLink(identity.address, this.props.network)}
@@ -58,36 +62,28 @@ class ConfirmRemoveAccount extends Component {
const { t } = this.context
return (
- <div className="modal-container">
- <div className="modal-container__content">
- <div className="modal-container__title">
- { `${t('removeAccount')}` }?
- </div>
- { this.renderSelectedAccount() }
- <div className="modal-container__description">
+ <Modal
+ headerText={`${t('removeAccount')}?`}
+ onClose={this.handleCancel}
+ onSubmit={this.handleRemove}
+ onCancel={this.handleCancel}
+ submitText={t('remove')}
+ cancelText={t('nevermind')}
+ submitType="secondary"
+ >
+ <div>
+ { this.renderSelectedAccount() }
+ <div className="confirm-remove-account__description">
{ t('removeAccountDescription') }
- <a className="modal-container__link" rel="noopener noreferrer" target="_blank" href="https://consensys.zendesk.com/hc/en-us/articles/360004180111-What-are-imported-accounts-New-UI-">{ t('learnMore') }</a>
+ <a
+ className="confirm-remove-account__link"
+ rel="noopener noreferrer"
+ target="_blank" href="https://consensys.zendesk.com/hc/en-us/articles/360004180111-What-are-imported-accounts-New-UI-">
+ { t('learnMore') }
+ </a>
</div>
</div>
- <div className="modal-container__footer">
- <Button
- type="default"
- className="modal-container__footer-button"
- onClick={() => this.props.hideModal()}
- >
- { t('nevermind') }
- </Button>
- <Button
- type="secondary"
- className="modal-container__footer-button"
- onClick={() => this.handleRemove()}
- >
- { t('remove') }
- </Button>
- </div>
- </div>
+ </Modal>
)
}
}
-
-export default ConfirmRemoveAccount
diff --git a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js
index 4b194c995..45c6654ab 100644
--- a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js
+++ b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js
@@ -1,20 +1,22 @@
import { connect } from 'react-redux'
+import { compose } from 'recompose'
import ConfirmRemoveAccount from './confirm-remove-account.component'
-
-const { hideModal, removeAccount } = require('../../../actions')
+import withModalProps from '../../../higher-order-components/with-modal-props'
+import { removeAccount } from '../../../actions'
const mapStateToProps = state => {
return {
- identity: state.appState.modal.modalState.props.identity,
network: state.metamask.network,
}
}
const mapDispatchToProps = dispatch => {
return {
- hideModal: () => dispatch(hideModal()),
removeAccount: (address) => dispatch(removeAccount(address)),
}
}
-export default connect(mapStateToProps, mapDispatchToProps)(ConfirmRemoveAccount)
+export default compose(
+ withModalProps,
+ connect(mapStateToProps, mapDispatchToProps)
+)(ConfirmRemoveAccount)
diff --git a/ui/app/components/modals/confirm-remove-account/index.js b/ui/app/components/modals/confirm-remove-account/index.js
index 9763fbe05..ecb5f7790 100644
--- a/ui/app/components/modals/confirm-remove-account/index.js
+++ b/ui/app/components/modals/confirm-remove-account/index.js
@@ -1,2 +1 @@
-import ConfirmRemoveAccount from './confirm-remove-account.container'
-module.exports = ConfirmRemoveAccount
+export { default } from './confirm-remove-account.container'
diff --git a/ui/app/components/modals/confirm-remove-account/index.scss b/ui/app/components/modals/confirm-remove-account/index.scss
new file mode 100644
index 000000000..3be3a1967
--- /dev/null
+++ b/ui/app/components/modals/confirm-remove-account/index.scss
@@ -0,0 +1,58 @@
+.confirm-remove-account {
+ &__description {
+ text-align: center;
+ font-size: .875rem;
+ }
+
+ &__account {
+ border: 1px solid #b7b7b7;
+ border-radius: 4px;
+ padding: 10px;
+ display: flex;
+ margin-top: 10px;
+ margin-bottom: 20px;
+ width: 100%;
+
+ &__identicon {
+ margin-right: 10px;
+ }
+
+ &__name,
+ &__address {
+ margin-right: 10px;
+ font-size: 14px;
+ }
+
+ &__name {
+ width: 100px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &__label {
+ font-size: 11px;
+ display: block;
+ color: #9b9b9b;
+ }
+
+ &__link {
+ margin-top: 14px;
+
+ img {
+ width: 15px;
+ height: 15px;
+ }
+ }
+
+ @media screen and (max-width: 575px) {
+ &__name {
+ width: 90px;
+ }
+ }
+ }
+
+ &__link {
+ color: #2f9ae0;
+ }
+} \ No newline at end of file
diff --git a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js b/ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js
index 14a4da62a..f1a4542ac 100644
--- a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js
+++ b/ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js
@@ -1,8 +1,8 @@
-import React, { Component } from 'react'
+import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import Button from '../../button'
+import Modal, { ModalContent } from '../../modal'
-class ConfirmResetAccount extends Component {
+export default class ConfirmResetAccount extends PureComponent {
static propTypes = {
hideModal: PropTypes.func.isRequired,
resetAccount: PropTypes.func.isRequired,
@@ -12,7 +12,7 @@ class ConfirmResetAccount extends Component {
t: PropTypes.func,
}
- handleReset () {
+ handleReset = () => {
this.props.resetAccount()
.then(() => this.props.hideModal())
}
@@ -21,34 +21,18 @@ class ConfirmResetAccount extends Component {
const { t } = this.context
return (
- <div className="modal-container">
- <div className="modal-container__content">
- <div className="modal-container__title">
- { `${t('resetAccount')}?` }
- </div>
- <div className="modal-container__description">
- { t('resetAccountDescription') }
- </div>
- </div>
- <div className="modal-container__footer">
- <Button
- type="default"
- className="modal-container__footer-button"
- onClick={() => this.props.hideModal()}
- >
- { t('nevermind') }
- </Button>
- <Button
- type="secondary"
- className="modal-container__footer-button"
- onClick={() => this.handleReset()}
- >
- { t('reset') }
- </Button>
- </div>
- </div>
+ <Modal
+ onSubmit={this.handleReset}
+ onCancel={() => this.props.hideModal()}
+ submitText={t('reset')}
+ cancelText={t('nevermind')}
+ submitType="secondary"
+ >
+ <ModalContent
+ title={`${t('resetAccount')}?`}
+ description={t('resetAccountDescription')}
+ />
+ </Modal>
)
}
}
-
-export default ConfirmResetAccount
diff --git a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js b/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js
index 9630a5593..c8a7b8478 100644
--- a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js
+++ b/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js
@@ -1,13 +1,16 @@
import { connect } from 'react-redux'
+import { compose } from 'recompose'
+import withModalProps from '../../../higher-order-components/with-modal-props'
import ConfirmResetAccount from './confirm-reset-account.component'
-
-const { hideModal, resetAccount } = require('../../../actions')
+import { resetAccount } from '../../../actions'
const mapDispatchToProps = dispatch => {
return {
- hideModal: () => dispatch(hideModal()),
resetAccount: () => dispatch(resetAccount()),
}
}
-export default connect(null, mapDispatchToProps)(ConfirmResetAccount)
+export default compose(
+ withModalProps,
+ connect(null, mapDispatchToProps)
+)(ConfirmResetAccount)
diff --git a/ui/app/components/modals/confirm-reset-account/index.js b/ui/app/components/modals/confirm-reset-account/index.js
index c812ffc55..ca4d9c5bf 100644
--- a/ui/app/components/modals/confirm-reset-account/index.js
+++ b/ui/app/components/modals/confirm-reset-account/index.js
@@ -1,2 +1 @@
-import ConfirmResetAccount from './confirm-reset-account.container'
-module.exports = ConfirmResetAccount
+export { default } from './confirm-reset-account.container'
diff --git a/ui/app/components/modals/index.scss b/ui/app/components/modals/index.scss
index 0acccf172..45453a582 100644
--- a/ui/app/components/modals/index.scss
+++ b/ui/app/components/modals/index.scss
@@ -1,108 +1,9 @@
-@import './customize-gas/index';
-
-@import './qr-scanner/index';
-
-.modal-container {
- width: 100%;
- height: 100%;
- background-color: #fff;
- display: flex;
- flex-flow: column;
- border-radius: 8px;
-
- &__title {
- font-size: 1.5rem;
- font-weight: 500;
- padding: 16px 0;
- text-align: center;
- }
-
- &__description {
- text-align: center;
- font-size: .875rem;
- }
-
- &__account {
- border: 1px solid #b7b7b7;
- border-radius: 4px;
- padding: 10px;
- display: flex;
- margin-top: 10px;
- margin-bottom: 20px;
- width: 100%;
-
- &__identicon {
- margin-right: 10px;
- }
-
- &__name,
- &__address {
- margin-right: 10px;
- font-size: 14px;
- }
+@import './cancel-transaction/index';
- &__name {
- width: 100px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
+@import './confirm-remove-account/index';
- &__label {
- font-size: 11px;
- display: block;
- color: #9b9b9b;
- }
-
- &__link {
- margin-top: 14px;
-
- img {
- width: 15px;
- height: 15px;
- }
- }
-
- @media screen and (max-width: 575px) {
- &__name {
- width: 90px;
- }
- }
- }
-
- &__link {
- color: #2f9ae0;
- }
-
- &__content {
- overflow-y: auto;
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 32px;
-
- @media screen and (max-width: 575px) {
- justify-content: center;
- padding: 28px 20px;
- }
- }
-
- &__footer {
- display: flex;
- flex-flow: row;
- justify-content: center;
- border-top: 1px solid #d2d8dd;
- padding: 16px;
- flex: 0 0 auto;
+@import './customize-gas/index';
- &-button {
- min-width: 0;
- margin-right: 16px;
+@import './qr-scanner/index';
- &:last-of-type {
- margin-right: 0;
- }
- }
- }
-}
+@import './transaction-confirmed/index';
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
index 5dda50e52..6054002c8 100644
--- a/ui/app/components/modals/modal.js
+++ b/ui/app/components/modals/modal.js
@@ -19,14 +19,15 @@ 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 ConfirmResetAccount = require('./confirm-reset-account')
-const ConfirmRemoveAccount = require('./confirm-remove-account')
const QRScanner = require('./qr-scanner')
-const TransactionConfirmed = require('./transaction-confirmed')
-const WelcomeBeta = require('./welcome-beta')
-const Notification = require('./notification')
+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 TransactionDetails from './transaction-details'
const modalContainerBaseStyle = {
transform: 'translate3d(-50%, 0, 0px)',
@@ -199,11 +200,7 @@ const MODALS = {
},
BETA_UI_NOTIFICATION_MODAL: {
- contents: [
- h(Notification, [
- h(WelcomeBeta),
- ]),
- ],
+ contents: h(WelcomeBeta),
mobileModalStyle: {
...modalContainerMobileStyle,
},
@@ -307,9 +304,7 @@ const MODALS = {
},
CONFIRM_CUSTOMIZE_GAS: {
- contents: [
- h(ConfirmCustomizeGasModal),
- ],
+ contents: h(ConfirmCustomizeGasModal),
mobileModalStyle: {
width: '100vw',
height: '100vh',
@@ -332,11 +327,7 @@ const MODALS = {
TRANSACTION_CONFIRMED: {
disableBackdropClick: true,
- contents: [
- h(Notification, [
- h(TransactionConfirmed),
- ]),
- ],
+ contents: h(TransactionConfirmed),
mobileModalStyle: {
...modalContainerMobileStyle,
},
@@ -347,6 +338,7 @@ const MODALS = {
borderRadius: '8px',
},
},
+
QR_SCANNER: {
contents: h(QRScanner),
mobileModalStyle: {
@@ -360,6 +352,32 @@ const MODALS = {
},
},
+ CANCEL_TRANSACTION: {
+ contents: h(CancelTransaction),
+ mobileModalStyle: {
+ ...modalContainerMobileStyle,
+ },
+ laptopModalStyle: {
+ ...modalContainerLaptopStyle,
+ },
+ contentStyle: {
+ borderRadius: '8px',
+ },
+ },
+
+ TRANSACTION_DETAILS: {
+ contents: h(TransactionDetails),
+ mobileModalStyle: {
+ ...modalContainerMobileStyle,
+ },
+ laptopModalStyle: {
+ ...modalContainerLaptopStyle,
+ },
+ contentStyle: {
+ borderRadius: '8px',
+ },
+ },
+
DEFAULT: {
contents: [],
mobileModalStyle: {},
diff --git a/ui/app/components/modals/notification/index.js b/ui/app/components/modals/notification/index.js
deleted file mode 100644
index d60a3129b..000000000
--- a/ui/app/components/modals/notification/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import Notification from './notification.container'
-module.exports = Notification
diff --git a/ui/app/components/modals/notification/notification.component.js b/ui/app/components/modals/notification/notification.component.js
deleted file mode 100644
index 1af2f3ca8..000000000
--- a/ui/app/components/modals/notification/notification.component.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import Button from '../../button'
-
-const Notification = (props, context) => {
- return (
- <div className="modal-container">
- { props.children }
- <div className="modal-container__footer">
- <Button
- type="primary"
- onClick={() => props.onHide()}
- >
- { context.t('ok') }
- </Button>
- </div>
- </div>
- )
-}
-
-Notification.propTypes = {
- onHide: PropTypes.func.isRequired,
- children: PropTypes.element,
-}
-
-Notification.contextTypes = {
- t: PropTypes.func,
-}
-
-export default Notification
diff --git a/ui/app/components/modals/notification/notification.container.js b/ui/app/components/modals/notification/notification.container.js
deleted file mode 100644
index 5b98714da..000000000
--- a/ui/app/components/modals/notification/notification.container.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import { connect } from 'react-redux'
-import Notification from './notification.component'
-
-const { hideModal } = require('../../../actions')
-
-const mapStateToProps = state => {
- const { appState: { modal: { modalState: { props } } } } = state
- const { onHide } = props
- return {
- onHide,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- hideModal: () => dispatch(hideModal()),
- }
-}
-
-const mergeProps = (stateProps, dispatchProps, ownProps) => {
- const { onHide, ...otherStateProps } = stateProps
- const { hideModal, ...otherDispatchProps } = dispatchProps
-
- return {
- ...otherStateProps,
- ...otherDispatchProps,
- ...ownProps,
- onHide: () => {
- hideModal()
-
- if (onHide && typeof onHide === 'function') {
- onHide()
- }
- },
- }
-}
-
-export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Notification)
diff --git a/ui/app/components/modals/transaction-confirmed/index.js b/ui/app/components/modals/transaction-confirmed/index.js
index cee8da7f8..7776b969e 100644
--- a/ui/app/components/modals/transaction-confirmed/index.js
+++ b/ui/app/components/modals/transaction-confirmed/index.js
@@ -1,2 +1 @@
-import TransactionConfirmed from './transaction-confirmed.component'
-module.exports = TransactionConfirmed
+export { default } from './transaction-confirmed.container'
diff --git a/ui/app/components/modals/transaction-confirmed/index.scss b/ui/app/components/modals/transaction-confirmed/index.scss
new file mode 100644
index 000000000..c97371fb6
--- /dev/null
+++ b/ui/app/components/modals/transaction-confirmed/index.scss
@@ -0,0 +1,22 @@
+.transaction-confirmed {
+ &__title {
+ font-size: 1.5rem;
+ font-weight: 500;
+ padding: 16px 0;
+ text-align: center;
+ }
+
+ &__description {
+ text-align: center;
+ font-size: .875rem;
+ }
+
+ &__content {
+ overflow-y: auto;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 16px;
+ }
+}
diff --git a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js
index c1c8a2976..0a98eb1a1 100644
--- a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js
+++ b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js
@@ -1,24 +1,45 @@
-import React from 'react'
+import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
+import Modal from '../../modal'
-const TransactionConfirmed = (props, context) => {
- const { t } = context
+export default class TransactionConfirmed extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
- return (
- <div className="modal-container__content">
- <img src="images/check-icon.svg" />
- <div className="modal-container__title">
- { `${t('confirmed')}!` }
- </div>
- <div className="modal-container__description">
- { t('initialTransactionConfirmed') }
- </div>
- </div>
- )
-}
+ static propTypes = {
+ onSubmit: PropTypes.func,
+ hideModal: PropTypes.func,
+ }
-TransactionConfirmed.contextTypes = {
- t: PropTypes.func,
-}
+ handleSubmit = () => {
+ const { hideModal, onSubmit } = this.props
+
+ hideModal()
-export default TransactionConfirmed
+ if (onSubmit && typeof onSubmit === 'function') {
+ onSubmit()
+ }
+ }
+
+ render () {
+ const { t } = this.context
+
+ return (
+ <Modal
+ onSubmit={this.handleSubmit}
+ submitText={t('ok')}
+ >
+ <div className="transaction-confirmed__content">
+ <img src="images/check-icon.svg" />
+ <div className="transaction-confirmed__title">
+ { `${t('confirmed')}!` }
+ </div>
+ <div className="transaction-confirmed__description">
+ { t('initialTransactionConfirmed') }
+ </div>
+ </div>
+ </Modal>
+ )
+ }
+}
diff --git a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js
new file mode 100644
index 000000000..d4e39681a
--- /dev/null
+++ b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js
@@ -0,0 +1,4 @@
+import TransactionConfirmed from './transaction-confirmed.component'
+import withModalProps from '../../../higher-order-components/with-modal-props'
+
+export default withModalProps(TransactionConfirmed)
diff --git a/ui/app/components/modals/transaction-details/index.js b/ui/app/components/modals/transaction-details/index.js
new file mode 100644
index 000000000..1fc42c662
--- /dev/null
+++ b/ui/app/components/modals/transaction-details/index.js
@@ -0,0 +1 @@
+export { default } from './transaction-details.container'
diff --git a/ui/app/components/modals/transaction-details/transaction-details.component.js b/ui/app/components/modals/transaction-details/transaction-details.component.js
new file mode 100644
index 000000000..f2fec3409
--- /dev/null
+++ b/ui/app/components/modals/transaction-details/transaction-details.component.js
@@ -0,0 +1,54 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Modal from '../../modal'
+import TransactionListItemDetails from '../../transaction-list-item-details'
+import { hexToDecimal } from '../../../helpers/conversions.util'
+
+export default class TransactionConfirmed extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ hideModal: PropTypes.func,
+ transaction: PropTypes.object,
+ onRetry: PropTypes.func,
+ showRetry: PropTypes.bool,
+ onCancel: PropTypes.func,
+ showCancel: PropTypes.bool,
+ }
+
+ handleSubmit = () => {
+ this.props.hideModal()
+ }
+
+ handleRetry = () => {
+ const { onRetry, hideModal } = this.props
+
+ Promise.resolve(onRetry()).then(() => hideModal())
+ }
+
+ render () {
+ const { t } = this.context
+ const { transaction, showRetry, onCancel, showCancel } = this.props
+ const { txParams: { nonce } = {} } = transaction
+ const decimalNonce = nonce && hexToDecimal(nonce)
+
+ return (
+ <Modal
+ onSubmit={this.handleSubmit}
+ onClose={this.handleSubmit}
+ submitText={t('ok')}
+ headerText={t('transactionWithNonce', [`#${decimalNonce}`])}
+ >
+ <TransactionListItemDetails
+ transaction={transaction}
+ onRetry={this.handleRetry}
+ showRetry={showRetry}
+ onCancel={() => onCancel()}
+ showCancel={showCancel}
+ />
+ </Modal>
+ )
+ }
+}
diff --git a/ui/app/components/modals/transaction-details/transaction-details.container.js b/ui/app/components/modals/transaction-details/transaction-details.container.js
new file mode 100644
index 000000000..f212920bb
--- /dev/null
+++ b/ui/app/components/modals/transaction-details/transaction-details.container.js
@@ -0,0 +1,4 @@
+import TransactionDetails from './transaction-details.component'
+import withModalProps from '../../../higher-order-components/with-modal-props'
+
+export default withModalProps(TransactionDetails)
diff --git a/ui/app/components/modals/welcome-beta/index.js b/ui/app/components/modals/welcome-beta/index.js
index 515c9cdaf..49e45b9d7 100644
--- a/ui/app/components/modals/welcome-beta/index.js
+++ b/ui/app/components/modals/welcome-beta/index.js
@@ -1,2 +1 @@
-import WelcomeBeta from './welcome-beta.component'
-module.exports = WelcomeBeta
+export { default } from './welcome-beta.container'
diff --git a/ui/app/components/modals/welcome-beta/welcome-beta.component.js b/ui/app/components/modals/welcome-beta/welcome-beta.component.js
index 61571723a..ef1799164 100644
--- a/ui/app/components/modals/welcome-beta/welcome-beta.component.js
+++ b/ui/app/components/modals/welcome-beta/welcome-beta.component.js
@@ -1,18 +1,21 @@
import React from 'react'
import PropTypes from 'prop-types'
+import Modal, { ModalContent } from '../../modal'
const TransactionConfirmed = (props, context) => {
const { t } = context
+ const { hideModal } = props
return (
- <div className="modal-container__content">
- <div className="modal-container__title">
- { `${t('uiWelcome')}` }
- </div>
- <div className="modal-container__description">
- { t('uiWelcomeMessage') }
- </div>
- </div>
+ <Modal
+ onSubmit={() => hideModal()}
+ submitText={t('ok')}
+ >
+ <ModalContent
+ title={t('uiWelcome')}
+ description={t('uiWelcomeMessage')}
+ />
+ </Modal>
)
}
@@ -20,4 +23,8 @@ TransactionConfirmed.contextTypes = {
t: PropTypes.func,
}
+TransactionConfirmed.propTypes = {
+ hideModal: PropTypes.func,
+}
+
export default TransactionConfirmed
diff --git a/ui/app/components/modals/welcome-beta/welcome-beta.container.js b/ui/app/components/modals/welcome-beta/welcome-beta.container.js
new file mode 100644
index 000000000..c5123ad47
--- /dev/null
+++ b/ui/app/components/modals/welcome-beta/welcome-beta.container.js
@@ -0,0 +1,4 @@
+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/page-container/index.scss b/ui/app/components/page-container/index.scss
index 14cdbacd3..61434cbcf 100644
--- a/ui/app/components/page-container/index.scss
+++ b/ui/app/components/page-container/index.scss
@@ -182,5 +182,7 @@
max-height: 82vh;
min-height: 570px;
flex: 0 0 auto;
+ margin-right: auto;
+ margin-left: auto;
}
}
diff --git a/ui/app/components/page-container/page-container-footer/tests/page-container-footer.component.test.js b/ui/app/components/page-container/page-container-footer/tests/page-container-footer.component.test.js
index e69de29bb..5e5dbf00b 100644
--- a/ui/app/components/page-container/page-container-footer/tests/page-container-footer.component.test.js
+++ b/ui/app/components/page-container/page-container-footer/tests/page-container-footer.component.test.js
@@ -0,0 +1,69 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import sinon from 'sinon'
+import Button from '../../../button'
+import PageFooter from '../page-container-footer.component'
+
+describe('Page Footer', () => {
+ let wrapper
+ const onCancel = sinon.spy()
+ const onSubmit = sinon.spy()
+
+ beforeEach(() => {
+ wrapper = shallow(<PageFooter
+ onCancel = {onCancel}
+ onSubmit = {onSubmit}
+ cancelText = {'Cancel'}
+ submitText = {'Submit'}
+ disabled = {false}
+ submitButtonType = {'Test Type'}
+ />)
+ })
+
+ it('renders page container footer', () => {
+ assert.equal(wrapper.find('.page-container__footer').length, 1)
+ })
+
+ it('renders two button components', () => {
+ assert.equal(wrapper.find(Button).length, 2)
+ })
+
+ describe('Cancel Button', () => {
+
+ it('has button type of default', () => {
+ assert.equal(wrapper.find('.page-container__footer-button').first().prop('type'), 'default')
+ })
+
+ it('has children text of Cancel', () => {
+ assert.equal(wrapper.find('.page-container__footer-button').first().prop('children'), 'Cancel')
+ })
+
+ it('should call cancel when click is simulated', () => {
+ wrapper.find('.page-container__footer-button').first().prop('onClick')()
+ assert.equal(onCancel.callCount, 1)
+ })
+
+ })
+
+ describe('Submit Button', () => {
+
+ it('assigns button type based on props', () => {
+ assert.equal(wrapper.find('.page-container__footer-button').last().prop('type'), 'Test Type')
+ })
+
+ it('has disabled prop', () => {
+ assert.equal(wrapper.find('.page-container__footer-button').last().prop('disabled'), false)
+ })
+
+ it('has children text when submitText prop exists', () => {
+ assert.equal(wrapper.find('.page-container__footer-button').last().prop('children'), 'Submit')
+ })
+
+ it('should call submit when click is simulated', () => {
+ wrapper.find('.page-container__footer-button').last().prop('onClick')()
+ assert.equal(onSubmit.callCount, 1)
+ })
+ })
+
+})
diff --git a/ui/app/components/page-container/page-container-header/tests/page-container-header.component.test.js b/ui/app/components/page-container/page-container-header/tests/page-container-header.component.test.js
index e69de29bb..59304b2bd 100644
--- a/ui/app/components/page-container/page-container-header/tests/page-container-header.component.test.js
+++ b/ui/app/components/page-container/page-container-header/tests/page-container-header.component.test.js
@@ -0,0 +1,82 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import sinon from 'sinon'
+import PageContainerHeader from '../page-container-header.component'
+
+describe('Page Container Header', () => {
+ let wrapper, style, onBackButtonClick, onClose
+
+ beforeEach(() => {
+ style = {test: 'style'}
+ onBackButtonClick = sinon.spy()
+ onClose = sinon.spy()
+
+ wrapper = shallow(<PageContainerHeader
+ showBackButton = {true}
+ onBackButtonClick = {onBackButtonClick}
+ backButtonStyles = {style}
+ title = {'Test Title'}
+ subtitle = {'Test Subtitle'}
+ tabs = {'Test Tab'}
+ onClose = {onClose}
+ />)
+ })
+
+ describe('Render Header Row', () => {
+
+ it('renders back button', () => {
+ assert.equal(wrapper.find('.page-container__back-button').length, 1)
+ assert.equal(wrapper.find('.page-container__back-button').text(), 'Back')
+ })
+
+ it('ensures style prop', () => {
+ assert.equal(wrapper.find('.page-container__back-button').props().style, style)
+ })
+
+ it('should call back button when click is simulated', () => {
+ wrapper.find('.page-container__back-button').prop('onClick')()
+ assert.equal(onBackButtonClick.callCount, 1)
+ })
+ })
+
+ describe('Render', () => {
+ let header, headerRow, pageTitle, pageSubtitle, pageClose, pageTab
+
+ beforeEach(() => {
+ header = wrapper.find('.page-container__header--no-padding-bottom')
+ headerRow = wrapper.find('.page-container__header-row')
+ pageTitle = wrapper.find('.page-container__title')
+ pageSubtitle = wrapper.find('.page-container__subtitle')
+ pageClose = wrapper.find('.page-container__header-close')
+ pageTab = wrapper.find('.page-container__tabs')
+ })
+
+ it('renders page container', () => {
+ assert.equal(header.length, 1)
+ assert.equal(headerRow.length, 1)
+ assert.equal(pageTitle.length, 1)
+ assert.equal(pageSubtitle.length, 1)
+ assert.equal(pageClose.length, 1)
+ assert.equal(pageTab.length, 1)
+ })
+
+ it('renders title', () => {
+ assert.equal(pageTitle.text(), 'Test Title')
+ })
+
+ it('renders subtitle', () => {
+ assert.equal(pageSubtitle.text(), 'Test Subtitle')
+ })
+
+ it('renders tabs', () => {
+ assert.equal(pageTab.text(), 'Test Tab')
+ })
+
+ it('should call close when click is simulated', () => {
+ pageClose.prop('onClick')()
+ assert.equal(onClose.callCount, 1)
+ })
+ })
+
+})
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
index 56cfbccc8..40d8faf50 100644
--- 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
@@ -8,6 +8,7 @@ import {
INSUFFICIENT_FUNDS_ERROR_KEY,
TRANSACTION_ERROR_KEY,
} from '../../../constants/error-keys'
+import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../../constants/transactions'
export default class ConfirmTransactionBase extends Component {
static contextTypes = {
@@ -85,9 +86,9 @@ export default class ConfirmTransactionBase extends Component {
clearConfirmTransaction,
} = this.props
- if (transactionStatus === 'dropped') {
+ if (transactionStatus === DROPPED_STATUS || transactionStatus === CONFIRMED_STATUS) {
showTransactionConfirmedModal({
- onHide: () => {
+ onSubmit: () => {
clearConfirmTransaction()
history.push(DEFAULT_ROUTE)
},
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
index 8f54c8040..ae31eba17 100644
--- 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
@@ -97,8 +97,8 @@ const mapDispatchToProps = dispatch => {
return {
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
clearSend: () => dispatch(clearSend()),
- showTransactionConfirmedModal: ({ onHide }) => {
- return dispatch(showModal({ name: 'TRANSACTION_CONFIRMED', onHide }))
+ showTransactionConfirmedModal: ({ onSubmit }) => {
+ return dispatch(showModal({ name: 'TRANSACTION_CONFIRMED', onSubmit }))
},
showCustomizeGasModal: ({ txData, onSubmit, validate }) => {
return dispatch(showModal({ name: 'CONFIRM_CUSTOMIZE_GAS', txData, onSubmit, validate }))
diff --git a/ui/app/components/pages/index.scss b/ui/app/components/pages/index.scss
index b15c59863..6551278f5 100644
--- a/ui/app/components/pages/index.scss
+++ b/ui/app/components/pages/index.scss
@@ -3,3 +3,5 @@
@import './add-token/index';
@import './confirm-add-token/index';
+
+@import './settings/index';
diff --git a/ui/app/components/pages/settings/index.js b/ui/app/components/pages/settings/index.js
index aee17e0e8..44a9ffa63 100644
--- a/ui/app/components/pages/settings/index.js
+++ b/ui/app/components/pages/settings/index.js
@@ -1,64 +1 @@
-const { Component } = require('react')
-const { Switch, Route, matchPath } = require('react-router-dom')
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const TabBar = require('../../tab-bar')
-const Settings = require('./settings')
-const Info = require('./info')
-const { DEFAULT_ROUTE, SETTINGS_ROUTE, INFO_ROUTE } = require('../../../routes')
-
-class Config extends Component {
- renderTabs () {
- const { history, location } = this.props
-
- return h('div.settings__tabs', [
- h(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),
- }),
- ])
- }
-
- render () {
- const { history } = this.props
-
- return (
- h('.main-container.settings', {}, [
- h('.settings__header', [
- h('div.settings__close-button', {
- onClick: () => history.push(DEFAULT_ROUTE),
- }),
- this.renderTabs(),
- ]),
- h(Switch, [
- h(Route, {
- exact: true,
- path: INFO_ROUTE,
- component: Info,
- }),
- h(Route, {
- exact: true,
- path: SETTINGS_ROUTE,
- component: Settings,
- }),
- ]),
- ])
- )
- }
-}
-
-Config.propTypes = {
- location: PropTypes.object,
- history: PropTypes.object,
- t: PropTypes.func,
-}
-
-Config.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = Config
+export { default } from './settings.component'
diff --git a/ui/app/components/pages/settings/index.scss b/ui/app/components/pages/settings/index.scss
new file mode 100644
index 000000000..138ebcfc5
--- /dev/null
+++ b/ui/app/components/pages/settings/index.scss
@@ -0,0 +1,80 @@
+@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
new file mode 100644
index 000000000..7556a258d
--- /dev/null
+++ b/ui/app/components/pages/settings/info-tab/index.js
@@ -0,0 +1 @@
+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
new file mode 100644
index 000000000..43ad6f652
--- /dev/null
+++ b/ui/app/components/pages/settings/info-tab/index.scss
@@ -0,0 +1,56 @@
+.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
new file mode 100644
index 000000000..72f7d835e
--- /dev/null
+++ b/ui/app/components/pages/settings/info-tab/info-tab.component.js
@@ -0,0 +1,136 @@
+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/info.js b/ui/app/components/pages/settings/info.js
deleted file mode 100644
index bd9040499..000000000
--- a/ui/app/components/pages/settings/info.js
+++ /dev/null
@@ -1,120 +0,0 @@
-const { Component } = require('react')
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-
-class Info extends Component {
- constructor (props) {
- super(props)
-
- this.state = {
- version: global.platform.getVersion(),
- }
- }
-
- renderLogo () {
- return (
- h('div.settings__info-logo-wrapper', [
- h('img.settings__info-logo', { src: 'images/info-logo.png' }),
- ])
- )
- }
-
- renderInfoLinks () {
- return (
- h('div.settings__content-item.settings__content-item--without-height', [
- h('div.settings__info-link-header', this.context.t('links')),
- h('div.settings__info-link-item', [
- h('a', {
- href: 'https://metamask.io/privacy.html',
- target: '_blank',
- }, [
- h('span.settings__info-link', this.context.t('privacyMsg')),
- ]),
- ]),
- h('div.settings__info-link-item', [
- h('a', {
- href: 'https://metamask.io/terms.html',
- target: '_blank',
- }, [
- h('span.settings__info-link', this.context.t('terms')),
- ]),
- ]),
- h('div.settings__info-link-item', [
- h('a', {
- href: 'https://metamask.io/attributions.html',
- target: '_blank',
- }, [
- h('span.settings__info-link', this.context.t('attributions')),
- ]),
- ]),
- h('hr.settings__info-separator'),
- h('div.settings__info-link-item', [
- h('a', {
- href: 'https://support.metamask.io',
- target: '_blank',
- }, [
- h('span.settings__info-link', this.context.t('supportCenter')),
- ]),
- ]),
- h('div.settings__info-link-item', [
- h('a', {
- href: 'https://metamask.io/',
- target: '_blank',
- }, [
- h('span.settings__info-link', this.context.t('visitWebSite')),
- ]),
- ]),
- h('div.settings__info-link-item', [
- h('a', {
- target: '_blank',
- href: 'mailto:help@metamask.io?subject=Feedback',
- }, [
- h('span.settings__info-link', this.context.t('emailUs')),
- ]),
- ]),
- ])
- )
- }
-
- render () {
- return (
- h('div.settings__content', [
- h('div.settings__content-row', [
- h('div.settings__content-item.settings__content-item--without-height', [
- this.renderLogo(),
- h('div.settings__info-item', [
- h('div.settings__info-version-header', 'MetaMask Version'),
- h('div.settings__info-version-number', this.state.version),
- ]),
- h('div.settings__info-item', [
- h(
- 'div.settings__info-about',
- this.context.t('builtInCalifornia')
- ),
- ]),
- ]),
- this.renderInfoLinks(),
- ]),
- ])
- )
- }
-}
-
-Info.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,
- t: PropTypes.func,
-}
-
-Info.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = Info
diff --git a/ui/app/components/pages/settings/settings-tab/index.js b/ui/app/components/pages/settings/settings-tab/index.js
new file mode 100644
index 000000000..9fdaafd3f
--- /dev/null
+++ b/ui/app/components/pages/settings/settings-tab/index.js
@@ -0,0 +1 @@
+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
new file mode 100644
index 000000000..76a0cec6f
--- /dev/null
+++ b/ui/app/components/pages/settings/settings-tab/index.scss
@@ -0,0 +1,51 @@
+.settings-tab {
+ &__error {
+ padding-bottom: 20px;
+ text-align: center;
+ color: $crimson;
+ }
+
+ &__rpc-save-button {
+ align-self: flex-end;
+ padding: 5px;
+ text-transform: uppercase;
+ color: $dusty-gray;
+ cursor: pointer;
+ }
+
+ &__rpc-save-button {
+ align-self: flex-end;
+ padding: 5px;
+ text-transform: uppercase;
+ color: $dusty-gray;
+ cursor: pointer;
+ }
+
+ &__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;
+ }
+ }
+}
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
new file mode 100644
index 000000000..53c4f16e0
--- /dev/null
+++ b/ui/app/components/pages/settings/settings-tab/settings-tab.component.js
@@ -0,0 +1,359 @@
+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,
+ setCurrentCurrency: PropTypes.func,
+ setRpcTarget: PropTypes.func,
+ displayWarning: PropTypes.func,
+ revealSeedConfirmation: PropTypes.func,
+ setFeatureFlagToBeta: 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,
+ }
+
+ state = {
+ newRpc: '',
+ }
+
+ 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 } = this.state
+
+ return (
+ <div className="settings-page__content-row">
+ <div className="settings-page__content-item">
+ <span>{ t('newRPC') }</span>
+ </div>
+ <div className="settings-page__content-item">
+ <div className="settings-page__content-item-col">
+ <TextField
+ type="text"
+ id="new-rpc"
+ placeholder={t('newRPC')}
+ value={newRpc}
+ onChange={e => this.setState({ newRpc: e.target.value })}
+ onKeyPress={e => {
+ if (e.key === 'Enter') {
+ this.validateRpc(newRpc)
+ }
+ }}
+ fullWidth
+ margin="none"
+ />
+ <div
+ className="settings-tab__rpc-save-button"
+ onClick={e => {
+ e.preventDefault()
+ this.validateRpc(newRpc)
+ }}
+ >
+ { t('save') }
+ </div>
+ </div>
+ </div>
+ </div>
+ )
+ }
+
+ validateRpc (newRpc) {
+ const { setRpcTarget, displayWarning } = this.props
+
+ if (validUrl.isWebUri(newRpc)) {
+ setRpcTarget(newRpc)
+ } 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>
+ )
+ }
+
+ 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>
+ )
+ }
+
+ render () {
+ const { warning, isMascara } = this.props
+
+ return (
+ <div className="settings-page__content">
+ { warning && <div className="settings-tab__error">{ warning }</div> }
+ { this.renderCurrentConversion() }
+ { this.renderCurrentLocale() }
+ { this.renderNewRpcUrl() }
+ { this.renderStateLogs() }
+ { this.renderSeedWords() }
+ { !isMascara && this.renderOldUI() }
+ { this.renderResetAccount() }
+ { this.renderBlockieOptIn() }
+ { this.renderHexDataOptIn() }
+ </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
new file mode 100644
index 000000000..665b56f5c
--- /dev/null
+++ b/ui/app/components/pages/settings/settings-tab/settings-tab.container.js
@@ -0,0 +1,59 @@
+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,
+} from '../../../../actions'
+
+const mapStateToProps = state => {
+ const { appState: { warning }, metamask } = state
+ const {
+ currentCurrency,
+ conversionDate,
+ useBlockie,
+ featureFlags: { sendHexData } = {},
+ provider = {},
+ isMascara,
+ currentLocale,
+ } = metamask
+
+ return {
+ warning,
+ isMascara,
+ currentLocale,
+ currentCurrency,
+ conversionDate,
+ useBlockie,
+ sendHexData,
+ provider,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ setCurrentCurrency: currency => dispatch(setCurrentCurrency(currency)),
+ setRpcTarget: newRpc => dispatch(setRpcTarget(newRpc)),
+ 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)),
+ showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),
+ }
+}
+
+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
new file mode 100644
index 000000000..94a97bba1
--- /dev/null
+++ b/ui/app/components/pages/settings/settings.component.js
@@ -0,0 +1,54 @@
+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/settings/settings.js b/ui/app/components/pages/settings/settings.js
deleted file mode 100644
index 423276cf3..000000000
--- a/ui/app/components/pages/settings/settings.js
+++ /dev/null
@@ -1,408 +0,0 @@
-const { Component } = require('react')
-const { withRouter } = require('react-router-dom')
-const { compose } = require('recompose')
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const connect = require('react-redux').connect
-const actions = require('../../../actions')
-const infuraCurrencies = require('../../../infura-conversion.json')
-const validUrl = require('valid-url')
-const { exportAsFile } = require('../../../util')
-const SimpleDropdown = require('../../dropdowns/simple-dropdown')
-const ToggleButton = require('react-toggle-button')
-const { REVEAL_SEED_ROUTE } = require('../../../routes')
-const locales = require('../../../../../app/_locales/index.json')
-
-import Button from '../../button'
-
-const getInfuraCurrencyOptions = () => {
- const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
- return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase())
- })
-
- return sortedCurrencies.map(({ quote: { code, name } }) => {
- return {
- displayValue: `${code.toUpperCase()} - ${name}`,
- key: code,
- value: code,
- }
- })
-}
-
-const getLocaleOptions = () => {
- return locales.map((locale) => {
- return {
- displayValue: `${locale.name}`,
- key: locale.code,
- value: locale.code,
- }
- })
-}
-
-class Settings extends Component {
- constructor (props) {
- super(props)
-
- this.state = {
- newRpc: '',
- }
- }
-
- renderBlockieOptIn () {
- const { metamask: { useBlockie }, setUseBlockie } = this.props
-
- return h('div.settings__content-row', [
- h('div.settings__content-item', [
- h('span', this.context.t('blockiesIdenticon')),
- ]),
- h('div.settings__content-item', [
- h('div.settings__content-item-col', [
- h(ToggleButton, {
- value: useBlockie,
- onToggle: (value) => setUseBlockie(!value),
- activeLabel: '',
- inactiveLabel: '',
- }),
- ]),
- ]),
- ])
- }
-
- renderHexDataOptIn () {
- const { metamask: { featureFlags: { sendHexData } }, setHexDataFeatureFlag } = this.props
-
- return h('div.settings__content-row', [
- h('div.settings__content-item', [
- h('span', this.context.t('showHexData')),
- h(
- 'div.settings__content-description',
- this.context.t('showHexDataDescription')
- ),
- ]),
- h('div.settings__content-item', [
- h('div.settings__content-item-col', [
- h(ToggleButton, {
- value: sendHexData,
- onToggle: (value) => setHexDataFeatureFlag(!value),
- activeLabel: '',
- inactiveLabel: '',
- }),
- ]),
- ]),
- ])
- }
-
- renderCurrentConversion () {
- const { metamask: { currentCurrency, conversionDate }, setCurrentCurrency } = this.props
-
- return h('div.settings__content-row', [
- h('div.settings__content-item', [
- h('span', this.context.t('currentConversion')),
- h('span.settings__content-description', `Updated ${Date(conversionDate)}`),
- ]),
- h('div.settings__content-item', [
- h('div.settings__content-item-col', [
- h(SimpleDropdown, {
- placeholder: this.context.t('selectCurrency'),
- options: getInfuraCurrencyOptions(),
- selectedOption: currentCurrency,
- onSelect: newCurrency => setCurrentCurrency(newCurrency),
- }),
- ]),
- ]),
- ])
- }
-
- renderCurrentLocale () {
- const { updateCurrentLocale, currentLocale } = this.props
- const currentLocaleMeta = locales.find(locale => locale.code === currentLocale)
- const currentLocaleName = currentLocaleMeta ? currentLocaleMeta.name : ''
-
- return h('div.settings__content-row', [
- h('div.settings__content-item', [
- h('span', 'Current Language'),
- h('span.settings__content-description', `${currentLocaleName}`),
- ]),
- h('div.settings__content-item', [
- h('div.settings__content-item-col', [
- h(SimpleDropdown, {
- placeholder: 'Select Locale',
- options: getLocaleOptions(),
- selectedOption: currentLocale,
- onSelect: async (newLocale) => {
- updateCurrentLocale(newLocale)
- },
- }),
- ]),
- ]),
- ])
- }
-
- renderCurrentProvider () {
- const { metamask: { provider = {} } } = this.props
- let title, value, color
-
- switch (provider.type) {
-
- case 'mainnet':
- title = this.context.t('currentNetwork')
- value = this.context.t('mainnet')
- color = '#038789'
- break
-
- case 'ropsten':
- title = this.context.t('currentNetwork')
- value = this.context.t('ropsten')
- color = '#e91550'
- break
-
- case 'kovan':
- title = this.context.t('currentNetwork')
- value = this.context.t('kovan')
- color = '#690496'
- break
-
- case 'rinkeby':
- title = this.context.t('currentNetwork')
- value = this.context.t('rinkeby')
- color = '#ebb33f'
- break
-
- default:
- title = this.context.t('currentRpc')
- value = provider.rpcTarget
- }
-
- return h('div.settings__content-row', [
- h('div.settings__content-item', title),
- h('div.settings__content-item', [
- h('div.settings__content-item-col', [
- h('div.settings__provider-wrapper', [
- h('div.settings__provider-icon', { style: { background: color } }),
- h('div', value),
- ]),
- ]),
- ]),
- ])
- }
-
- renderNewRpcUrl () {
- return (
- h('div.settings__content-row', [
- h('div.settings__content-item', [
- h('span', this.context.t('newRPC')),
- ]),
- h('div.settings__content-item', [
- h('div.settings__content-item-col', [
- h('input.settings__input', {
- placeholder: this.context.t('newRPC'),
- onChange: event => this.setState({ newRpc: event.target.value }),
- onKeyPress: event => {
- if (event.key === 'Enter') {
- this.validateRpc(this.state.newRpc)
- }
- },
- }),
- h('div.settings__rpc-save-button', {
- onClick: event => {
- event.preventDefault()
- this.validateRpc(this.state.newRpc)
- },
- }, this.context.t('save')),
- ]),
- ]),
- ])
- )
- }
-
- validateRpc (newRpc) {
- const { setRpcTarget, displayWarning } = this.props
-
- if (validUrl.isWebUri(newRpc)) {
- setRpcTarget(newRpc)
- } else {
- const appendedRpc = `http://${newRpc}`
-
- if (validUrl.isWebUri(appendedRpc)) {
- displayWarning(this.context.t('uriErrorMsg'))
- } else {
- displayWarning(this.context.t('invalidRPC'))
- }
- }
- }
-
- renderStateLogs () {
- return (
- h('div.settings__content-row', [
- h('div.settings__content-item', [
- h('div', this.context.t('stateLogs')),
- h(
- 'div.settings__content-description',
- this.context.t('stateLogsDescription')
- ),
- ]),
- h('div.settings__content-item', [
- h('div.settings__content-item-col', [
- h(Button, {
- type: 'primary',
- large: true,
- className: 'settings__button',
- onClick (event) {
- window.logStateString((err, result) => {
- if (err) {
- this.state.dispatch(actions.displayWarning(this.context.t('stateLogError')))
- } else {
- exportAsFile('MetaMask State Logs.json', result)
- }
- })
- },
- }, this.context.t('downloadStateLogs')),
- ]),
- ]),
- ])
- )
- }
-
- renderSeedWords () {
- const { history } = this.props
-
- return (
- h('div.settings__content-row', [
- h('div.settings__content-item', this.context.t('revealSeedWords')),
- h('div.settings__content-item', [
- h('div.settings__content-item-col', [
- h(Button, {
- type: 'primary',
- large: true,
- className: 'settings__button--red',
- onClick: event => {
- event.preventDefault()
- history.push(REVEAL_SEED_ROUTE)
- },
- }, this.context.t('revealSeedWords')),
- ]),
- ]),
- ])
- )
- }
-
- renderOldUI () {
- const { setFeatureFlagToBeta } = this.props
-
- return (
- h('div.settings__content-row', [
- h('div.settings__content-item', this.context.t('useOldUI')),
- h('div.settings__content-item', [
- h('div.settings__content-item-col', [
- h(Button, {
- type: 'primary',
- large: true,
- className: 'settings__button--orange',
- onClick (event) {
- event.preventDefault()
- setFeatureFlagToBeta()
- },
- }, this.context.t('useOldUI')),
- ]),
- ]),
- ])
- )
- }
-
- renderResetAccount () {
- const { showResetAccountConfirmationModal } = this.props
-
- return h('div.settings__content-row', [
- h('div.settings__content-item', this.context.t('resetAccount')),
- h('div.settings__content-item', [
- h('div.settings__content-item-col', [
- h(Button, {
- type: 'primary',
- large: true,
- className: 'settings__button--orange',
- onClick (event) {
- event.preventDefault()
- showResetAccountConfirmationModal()
- },
- }, this.context.t('resetAccount')),
- ]),
- ]),
- ])
- }
-
- render () {
- const { warning, isMascara } = this.props
-
- return (
- h('div.settings__content', [
- warning && h('div.settings__error', warning),
- this.renderCurrentConversion(),
- this.renderCurrentLocale(),
- // this.renderCurrentProvider(),
- this.renderNewRpcUrl(),
- this.renderStateLogs(),
- this.renderSeedWords(),
- !isMascara && this.renderOldUI(),
- this.renderResetAccount(),
- this.renderBlockieOptIn(),
- this.renderHexDataOptIn(),
- ])
- )
- }
-}
-
-Settings.propTypes = {
- metamask: PropTypes.object,
- setUseBlockie: PropTypes.func,
- setHexDataFeatureFlag: PropTypes.func,
- setCurrentCurrency: PropTypes.func,
- setRpcTarget: PropTypes.func,
- displayWarning: PropTypes.func,
- revealSeedConfirmation: PropTypes.func,
- setFeatureFlagToBeta: PropTypes.func,
- showResetAccountConfirmationModal: PropTypes.func,
- warning: PropTypes.string,
- history: PropTypes.object,
- isMascara: PropTypes.bool,
- updateCurrentLocale: PropTypes.func,
- currentLocale: PropTypes.string,
- t: PropTypes.func,
-}
-
-const mapStateToProps = state => {
- return {
- metamask: state.metamask,
- warning: state.appState.warning,
- isMascara: state.metamask.isMascara,
- currentLocale: state.metamask.currentLocale,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- setCurrentCurrency: currency => dispatch(actions.setCurrentCurrency(currency)),
- setRpcTarget: newRpc => dispatch(actions.setRpcTarget(newRpc)),
- displayWarning: warning => dispatch(actions.displayWarning(warning)),
- revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()),
- setUseBlockie: value => dispatch(actions.setUseBlockie(value)),
- updateCurrentLocale: key => dispatch(actions.updateCurrentLocale(key)),
- setFeatureFlagToBeta: () => {
- return dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
- },
- setHexDataFeatureFlag: (featureFlagShowState) => {
- return dispatch(actions.setFeatureFlag('sendHexData', featureFlagShowState))
- },
- showResetAccountConfirmationModal: () => {
- return dispatch(actions.showModal({ name: 'CONFIRM_RESET_ACCOUNT' }))
- },
- }
-}
-
-Settings.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(Settings)
diff --git a/ui/app/components/pages/unlock-page/index.scss b/ui/app/components/pages/unlock-page/index.scss
index 3d44bd037..6bd52282d 100644
--- a/ui/app/components/pages/unlock-page/index.scss
+++ b/ui/app/components/pages/unlock-page/index.scss
@@ -14,6 +14,7 @@
align-self: stretch;
justify-content: center;
flex: 1 0 auto;
+ height: 100vh;
}
&__mascot-container {
diff --git a/ui/app/components/send/currency-display/tests/currency-display.test.js b/ui/app/components/send/currency-display/tests/currency-display.test.js
new file mode 100644
index 000000000..c9560b81c
--- /dev/null
+++ b/ui/app/components/send/currency-display/tests/currency-display.test.js
@@ -0,0 +1,91 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { shallow, mount } from 'enzyme'
+import CurrencyDisplay from '../currency-display'
+
+describe('', () => {
+
+ const token = {
+ address: '0xTest',
+ symbol: 'TST',
+ decimals: '13',
+ }
+
+ it('retuns ETH value for wei value', () => {
+ const wrapper = mount(<CurrencyDisplay />, {context: {t: str => str + '_t'}})
+
+ const value = wrapper.instance().getValueToRender({
+ // 1000000000000000000
+ value: 'DE0B6B3A7640000',
+ })
+
+ assert.equal(value, 1)
+ })
+
+ it('returns value of token based on token decimals', () => {
+ const wrapper = mount(<CurrencyDisplay />, {context: {t: str => str + '_t'}})
+
+ const value = wrapper.instance().getValueToRender({
+ selectedToken: token,
+ // 1000000000000000000
+ value: 'DE0B6B3A7640000',
+ })
+
+ assert.equal(value, 100000)
+ })
+
+ it('returns hex value with decimal adjustment', () => {
+
+ const wrapper = mount(
+ <CurrencyDisplay
+ selectedToken={token}
+ />, {context: {t: str => str + '_t'}})
+
+ const value = wrapper.instance().getAmount(1)
+ // 10000000000000
+ assert.equal(value, '9184e72a000')
+ })
+
+ it('#getConvertedValueToRender converts input value based on conversionRate', () => {
+
+ const wrapper = mount(
+ <CurrencyDisplay
+ primaryCurrency={'usd'}
+ convertedCurrency={'ja'}
+ conversionRate={2}
+ />, {context: {t: str => str + '_t'}})
+
+ const value = wrapper.instance().getConvertedValueToRender(32)
+
+ assert.equal(value, 64)
+ })
+
+ it('#onlyRenderConversions renders single element for converted currency and value', () => {
+ const wrapper = mount(
+ <CurrencyDisplay
+ convertedCurrency={'test'}
+ />, {context: {t: str => str + '_t'}})
+
+ const value = wrapper.instance().onlyRenderConversions(10)
+ assert.equal(value.props.className, 'currency-display__converted-value')
+ assert.equal(value.props.children, '10 TEST')
+ })
+
+ it('simulates change value in input', () => {
+ const handleChangeSpy = sinon.spy()
+
+ const wrapper = shallow(
+ <CurrencyDisplay
+ onChange={handleChangeSpy}
+ />, {context: {t: str => str + '_t'}})
+
+ const input = wrapper.find('input')
+ input.simulate('focus')
+ input.simulate('change', { target: { value: '100' } })
+
+ assert.equal(wrapper.state().valueToRender, '100')
+ assert.equal(wrapper.find('input').prop('value'), '100')
+ })
+
+})
diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js
index 2bfa350d3..5b0c7684a 100644
--- a/ui/app/components/signature-request.js
+++ b/ui/app/components/signature-request.js
@@ -8,6 +8,7 @@ const ethUtil = require('ethereumjs-util')
const classnames = require('classnames')
const { compose } = require('recompose')
const { withRouter } = require('react-router-dom')
+const { ObjectInspector } = require('react-inspector')
const AccountDropdownMini = require('./dropdowns/account-dropdown-mini')
@@ -169,12 +170,29 @@ SignatureRequest.prototype.msgHexToText = function (hex) {
}
}
+// eslint-disable-next-line react/display-name
+SignatureRequest.prototype.renderTypedDataV3 = function (data) {
+ const { domain, message } = JSON.parse(data)
+ return [
+ h('div.request-signature__typed-container', [
+ domain ? h('div', [
+ h('h1', 'Domain'),
+ h(ObjectInspector, { data: domain, expandLevel: 1, name: 'domain' }),
+ ]) : '',
+ message ? h('div', [
+ h('h1', 'Message'),
+ h(ObjectInspector, { data: message, expandLevel: 1, name: 'message' }),
+ ]) : '',
+ ]),
+ ]
+}
+
SignatureRequest.prototype.renderBody = function () {
let rows
let notice = this.context.t('youSign') + ':'
const { txData } = this.props
- const { type, msgParams: { data } } = txData
+ const { type, msgParams: { data, version } } = txData
if (type === 'personal_sign') {
rows = [{ name: this.context.t('message'), value: this.msgHexToText(data) }]
@@ -205,9 +223,9 @@ SignatureRequest.prototype.renderBody = function () {
}),
}, [notice]),
- h('div.request-signature__rows', [
-
- ...rows.map(({ name, value }) => {
+ h('div.request-signature__rows', type === 'eth_signTypedData' && version === 'V3' ?
+ this.renderTypedDataV3(data) :
+ rows.map(({ name, value }) => {
if (typeof value === 'boolean') {
value = value.toString()
}
@@ -216,9 +234,7 @@ SignatureRequest.prototype.renderBody = function () {
h('div.request-signature__row-value', value),
])
}),
-
- ]),
-
+ ),
])
}
diff --git a/ui/app/components/transaction-action/tests/transaction-action.component.test.js b/ui/app/components/transaction-action/tests/transaction-action.component.test.js
index 218792847..9352c7b43 100644
--- a/ui/app/components/transaction-action/tests/transaction-action.component.test.js
+++ b/ui/app/components/transaction-action/tests/transaction-action.component.test.js
@@ -5,10 +5,9 @@ import sinon from 'sinon'
import TransactionAction from '../transaction-action.component'
describe('TransactionAction Component', () => {
- const tOrDefault = key => key
+ const t = key => key
global.eth = {
getCode: sinon.stub().callsFake(address => {
- console.log('CALLED')
const code = address === 'approveAddress' ? 'contract' : '0x'
return Promise.resolve(code)
}),
@@ -36,7 +35,7 @@ describe('TransactionAction Component', () => {
methodData={methodData}
transaction={transaction}
className="transaction-action"
- />, { context: { tOrDefault }})
+ />, { context: { t }})
assert.equal(wrapper.find('.transaction-action').length, 1)
assert.equal(wrapper.text(), '--')
@@ -63,7 +62,7 @@ describe('TransactionAction Component', () => {
methodData={methodData}
transaction={transaction}
className="transaction-action"
- />, { context: { tOrDefault }})
+ />, { context: { t }})
assert.equal(wrapper.find('.transaction-action').length, 1)
wrapper.setState({ transactionAction: 'sentEther' })
@@ -102,7 +101,7 @@ describe('TransactionAction Component', () => {
methodData={methodData}
transaction={transaction}
className="transaction-action"
- />, { context: { tOrDefault }})
+ />, { context: { t }})
assert.equal(wrapper.find('.transaction-action').length, 1)
wrapper.setState({ transactionAction: 'approve' })
diff --git a/ui/app/components/transaction-action/transaction-action.component.js b/ui/app/components/transaction-action/transaction-action.component.js
index 81a1e96d0..1729b878c 100644
--- a/ui/app/components/transaction-action/transaction-action.component.js
+++ b/ui/app/components/transaction-action/transaction-action.component.js
@@ -4,7 +4,7 @@ import { getTransactionActionKey } from '../../helpers/transactions.util'
export default class TransactionAction extends PureComponent {
static contextTypes = {
- tOrDefault: PropTypes.func,
+ t: PropTypes.func,
}
static propTypes = {
@@ -35,7 +35,7 @@ export default class TransactionAction extends PureComponent {
}
const actionKey = await getTransactionActionKey(transaction, data)
- const action = actionKey && this.context.tOrDefault(actionKey)
+ const action = actionKey && this.context.t(actionKey)
this.setState({ transactionAction: action })
}
diff --git a/ui/app/components/transaction-activity-log/index.scss b/ui/app/components/transaction-activity-log/index.scss
index 2324d44b1..27f3006b3 100644
--- a/ui/app/components/transaction-activity-log/index.scss
+++ b/ui/app/components/transaction-activity-log/index.scss
@@ -33,6 +33,10 @@
&:last-child::after {
height: 50%;
}
+
+ &:first-child:last-child::after {
+ display: none;
+ }
}
&__activity-icon {
@@ -47,9 +51,12 @@
&__activity-text {
color: $scorpion;
font-size: .75rem;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+
+ @media screen and (min-width: $break-large) {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
}
&__value {
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
index f65ff4d55..13cb51349 100644
--- 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
@@ -13,7 +13,9 @@ export default class TransactionListItemDetails extends PureComponent {
}
static propTypes = {
+ onCancel: PropTypes.func,
onRetry: PropTypes.func,
+ showCancel: PropTypes.bool,
showRetry: PropTypes.bool,
transaction: PropTypes.object,
}
@@ -27,6 +29,13 @@ export default class TransactionListItemDetails extends PureComponent {
this.setState({ showTransactionDetails: true })
}
+ handleCancel = event => {
+ const { onCancel } = this.props
+
+ event.stopPropagation()
+ onCancel()
+ }
+
handleRetry = event => {
const { onRetry } = this.props
@@ -36,7 +45,7 @@ export default class TransactionListItemDetails extends PureComponent {
render () {
const { t } = this.context
- const { transaction, showRetry } = this.props
+ const { transaction, showCancel, showRetry } = this.props
const { txParams: { to, from } = {} } = transaction
return (
@@ -55,6 +64,17 @@ export default class TransactionListItemDetails extends PureComponent {
</Button>
)
}
+ {
+ showCancel && (
+ <Button
+ type="raised"
+ onClick={this.handleCancel}
+ className="transaction-list-item-details__header-button"
+ >
+ { t('cancel') }
+ </Button>
+ )
+ }
<Button
type="raised"
onClick={this.handleEtherscanClick}
diff --git a/ui/app/components/transaction-list-item/index.scss b/ui/app/components/transaction-list-item/index.scss
index 427686c29..9d694546b 100644
--- a/ui/app/components/transaction-list-item/index.scss
+++ b/ui/app/components/transaction-list-item/index.scss
@@ -6,6 +6,7 @@
justify-content: center;
align-items: center;
flex-direction: column;
+ background: $white;
&__grid {
cursor: pointer;
@@ -117,4 +118,14 @@
background: #f3f4f7;
width: 100%;
}
+
+ &__expander {
+ max-height: 0px;
+ width: 100%;
+
+ &--show {
+ max-height: 1000px;
+ transition: max-height 700ms ease-out;
+ }
+ }
}
diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js
index e590e96e0..c1c69f59b 100644
--- a/ui/app/components/transaction-list-item/transaction-list-item.component.js
+++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js
@@ -1,5 +1,6 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
+import classnames from 'classnames'
import Identicon from '../identicon'
import TransactionStatus from '../transaction-status'
import TransactionAction from '../transaction-action'
@@ -9,20 +10,24 @@ import TransactionListItemDetails from '../transaction-list-item-details'
import { CONFIRM_TRANSACTION_ROUTE } from '../../routes'
import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions'
import { ETH } from '../../constants/common'
+import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../app/scripts/lib/enums'
export default class TransactionListItem extends PureComponent {
static propTypes = {
+ assetImages: PropTypes.object,
history: PropTypes.object,
- transaction: PropTypes.object,
- value: PropTypes.string,
methodData: PropTypes.object,
- showRetry: PropTypes.bool,
+ nonceAndDate: PropTypes.string,
retryTransaction: PropTypes.func,
setSelectedToken: PropTypes.func,
- nonceAndDate: PropTypes.string,
+ showCancelModal: PropTypes.func,
+ showCancel: PropTypes.bool,
+ showRetry: PropTypes.bool,
+ showTransactionDetailsModal: PropTypes.func,
token: PropTypes.object,
- assetImages: PropTypes.object,
tokenData: PropTypes.object,
+ transaction: PropTypes.object,
+ value: PropTypes.string,
}
state = {
@@ -30,16 +35,39 @@ export default class TransactionListItem extends PureComponent {
}
handleClick = () => {
- const { transaction, history } = this.props
+ const {
+ transaction,
+ history,
+ showTransactionDetailsModal,
+ methodData,
+ showCancel,
+ showRetry,
+ } = this.props
const { id, status } = transaction
const { showTransactionDetails } = this.state
+ const windowType = window.METAMASK_UI_TYPE
if (status === UNAPPROVED_STATUS) {
history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`)
return
}
- this.setState({ showTransactionDetails: !showTransactionDetails })
+ if (windowType === ENVIRONMENT_TYPE_FULLSCREEN) {
+ this.setState({ showTransactionDetails: !showTransactionDetails })
+ } else {
+ showTransactionDetailsModal({
+ transaction,
+ onRetry: this.handleRetry,
+ showRetry: showRetry && methodData.done,
+ onCancel: this.handleCancel,
+ showCancel,
+ })
+ }
+ }
+
+ handleCancel = () => {
+ const { transaction: { id, txParams: { gasPrice } } = {}, showCancelModal } = this.props
+ showCancelModal(id, gasPrice)
}
handleRetry = () => {
@@ -53,12 +81,12 @@ export default class TransactionListItem extends PureComponent {
setSelectedToken(to)
}
- this.resubmit()
+ return this.resubmit()
}
resubmit () {
const { transaction: { id }, retryTransaction, history } = this.props
- retryTransaction(id)
+ return retryTransaction(id)
.then(id => history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`))
}
@@ -100,12 +128,13 @@ export default class TransactionListItem extends PureComponent {
render () {
const {
- transaction,
+ assetImages,
methodData,
- showRetry,
nonceAndDate,
- assetImages,
+ showCancel,
+ showRetry,
tokenData,
+ transaction,
} = this.props
const { txParams = {} } = transaction
const { showTransactionDetails } = this.state
@@ -148,17 +177,23 @@ export default class TransactionListItem extends PureComponent {
{ this.renderPrimaryCurrency() }
{ this.renderSecondaryCurrency() }
</div>
- {
- showTransactionDetails && (
- <div className="transaction-list-item__details-container">
- <TransactionListItemDetails
- transaction={transaction}
- showRetry={showRetry && methodData.done}
- onRetry={this.handleRetry}
- />
- </div>
- )
- }
+ <div className={classnames('transaction-list-item__expander', {
+ 'transaction-list-item__expander--show': showTransactionDetails,
+ })}>
+ {
+ showTransactionDetails && (
+ <div className="transaction-list-item__details-container">
+ <TransactionListItemDetails
+ transaction={transaction}
+ onRetry={this.handleRetry}
+ showRetry={showRetry && methodData.done}
+ onCancel={this.handleCancel}
+ showCancel={showCancel}
+ />
+ </div>
+ )
+ }
+ </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
index 3db9d40ec..72f5f5d61 100644
--- a/ui/app/components/transaction-list-item/transaction-list-item.container.js
+++ b/ui/app/components/transaction-list-item/transaction-list-item.container.js
@@ -3,7 +3,7 @@ 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 } from '../../actions'
+import { setSelectedToken, retryTransaction, showModal } from '../../actions'
import { hexToDecimal } from '../../helpers/conversions.util'
import { getTokenData } from '../../helpers/transactions.util'
import { formatDate } from '../../util'
@@ -25,6 +25,19 @@ 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 }))
+ },
+ showTransactionDetailsModal: ({ transaction, onRetry, showRetry, onCancel, showCancel }) => {
+ return dispatch(showModal({
+ name: 'TRANSACTION_DETAILS',
+ transaction,
+ onRetry,
+ showRetry,
+ onCancel,
+ showCancel,
+ }))
+ },
}
}
diff --git a/ui/app/components/transaction-list/index.scss b/ui/app/components/transaction-list/index.scss
index d944ef20e..777f701f9 100644
--- a/ui/app/components/transaction-list/index.scss
+++ b/ui/app/components/transaction-list/index.scss
@@ -3,6 +3,8 @@
flex-direction: column;
flex: 1;
overflow-y: hidden;
+ margin-top: 8px;
+ border-top: 1px solid $geyser;
&__completed-transactions {
display: flex;
@@ -15,7 +17,7 @@
font-size: .875rem;
color: $dusty-gray;
border-bottom: 1px solid $geyser;
- padding: 16px 0 8px 20px;
+ padding: 8px 0 8px 20px;
@media screen and (max-width: $break-small) {
padding: 8px 0 8px 16px;
diff --git a/ui/app/components/transaction-list/transaction-list.component.js b/ui/app/components/transaction-list/transaction-list.component.js
index c864fea3b..eef60186d 100644
--- a/ui/app/components/transaction-list/transaction-list.component.js
+++ b/ui/app/components/transaction-list/transaction-list.component.js
@@ -56,7 +56,7 @@ export default class TransactionList extends PureComponent {
</div>
{
pendingTransactions.map((transaction, index) => (
- this.renderTransaction(transaction, index)
+ this.renderTransaction(transaction, index, true)
))
}
</div>
@@ -78,7 +78,7 @@ export default class TransactionList extends PureComponent {
)
}
- renderTransaction (transaction, index) {
+ renderTransaction (transaction, index, showCancel) {
const { selectedToken, assetImages } = this.props
return transaction.key === TRANSACTION_TYPE_SHAPESHIFT
@@ -92,6 +92,7 @@ export default class TransactionList extends PureComponent {
transaction={transaction}
key={transaction.id}
showRetry={this.shouldShowRetry(transaction)}
+ showCancel={showCancel}
token={selectedToken}
assetImages={assetImages}
/>
diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js
index 112ea6bca..0784a872e 100644
--- a/ui/app/conf-tx.js
+++ b/ui/app/conf-tx.js
@@ -104,7 +104,7 @@ ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) {
if (prevTx && prevTx.status === 'dropped') {
this.props.dispatch(actions.showModal({
name: 'TRANSACTION_CONFIRMED',
- onHide: () => history.push(DEFAULT_ROUTE),
+ onSubmit: () => history.push(DEFAULT_ROUTE),
}))
return
diff --git a/ui/app/constants/transactions.js b/ui/app/constants/transactions.js
index df6c4c8a4..2dc061091 100644
--- a/ui/app/constants/transactions.js
+++ b/ui/app/constants/transactions.js
@@ -18,5 +18,6 @@ export const SEND_TOKEN_ACTION_KEY = 'sentTokens'
export const TRANSFER_FROM_ACTION_KEY = 'transferFrom'
export const SIGNATURE_REQUEST_KEY = 'signatureRequest'
export const UNKNOWN_FUNCTION_KEY = 'unknownFunction'
+export const CANCEL_ATTEMPT_ACTION_KEY = 'cancelAttempt'
export const TRANSACTION_TYPE_SHAPESHIFT = 'shapeshift'
diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss
index 9e2008b54..99deaf918 100644
--- a/ui/app/css/itcss/components/index.scss
+++ b/ui/app/css/itcss/components/index.scss
@@ -36,8 +36,6 @@
@import './gas-slider.scss';
-@import './settings.scss';
-
@import './tab-bar.scss';
@import './simple-dropdown.scss';
diff --git a/ui/app/css/itcss/components/loading-overlay.scss b/ui/app/css/itcss/components/loading-overlay.scss
index b07d6af17..b023c8423 100644
--- a/ui/app/css/itcss/components/loading-overlay.scss
+++ b/ui/app/css/itcss/components/loading-overlay.scss
@@ -1,6 +1,6 @@
.loading-overlay {
left: 0;
- z-index: 50;
+ z-index: 51;
position: absolute;
flex-direction: column;
display: flex;
@@ -8,25 +8,9 @@
align-items: center;
flex: 1 1 auto;
width: 100%;
+ height: 100%;
background: rgba(255, 255, 255, .8);
- @media screen and (max-width: 575px) {
- margin-top: 66px;
- height: calc(100% - 66px);
- }
-
- @media screen and (min-width: 576px) {
- margin-top: 75px;
- height: calc(100% - 75px);
- }
-
- &--full-screen {
- position: fixed;
- height: 100vh;
- width: 100vw;
- margin-top: 0;
- }
-
&__container {
position: absolute;
top: 33%;
diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss
index 7eb193d6f..8e963d495 100644
--- a/ui/app/css/itcss/components/newui-sections.scss
+++ b/ui/app/css/itcss/components/newui-sections.scss
@@ -22,6 +22,12 @@ $wallet-view-bg: $alabaster;
display: none;
}
+.main-container-wrapper {
+ display: flex;
+ width: 100vw;
+ justify-content: center;
+}
+
//Account and transaction details
.account-and-transaction-details {
display: flex;
@@ -219,6 +225,10 @@ $wallet-view-bg: $alabaster;
overflow-y: auto;
background-color: $white;
}
+
+ .main-container-wrapper {
+ height: 100%;
+ }
}
// wallet view
diff --git a/ui/app/css/itcss/components/request-signature.scss b/ui/app/css/itcss/components/request-signature.scss
index b607aded3..445b9ebf5 100644
--- a/ui/app/css/itcss/components/request-signature.scss
+++ b/ui/app/css/itcss/components/request-signature.scss
@@ -23,6 +23,25 @@
}
}
+ &__typed-container {
+ padding: 17px;
+
+ h1 {
+ font-weight: 900;
+ margin-bottom: 5px;
+ }
+
+ * {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ > div {
+ margin-bottom: 10px;
+ }
+ }
+
&__header {
height: 64px;
width: 100%;
diff --git a/ui/app/css/itcss/components/settings.scss b/ui/app/css/itcss/components/settings.scss
deleted file mode 100644
index 0dd61ac5e..000000000
--- a/ui/app/css/itcss/components/settings.scss
+++ /dev/null
@@ -1,214 +0,0 @@
-.settings {
- position: relative;
- background: $white;
- display: flex;
- flex-flow: column nowrap;
-}
-
-.settings__header {
- padding: 25px;
-}
-
-.settings__close-button::after {
- content: '\00D7';
- font-size: 40px;
- color: $dusty-gray;
- position: absolute;
- top: 25px;
- right: 30px;
- cursor: pointer;
-}
-
-.settings__error {
- padding-bottom: 20px;
- text-align: center;
- color: $crimson;
-}
-
-.settings__content {
- padding: 0 25px;
- height: auto;
- overflow: auto;
-}
-
-.settings__content-row {
- display: flex;
- flex-direction: row;
- padding: 10px 0 20px;
-
- @media screen and (max-width: 575px) {
- flex-direction: column;
- padding: 10px 0;
- }
-}
-
-.settings__content-item {
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
- padding: 0 5px;
- height: 71px;
-
- @media screen and (max-width: 575px) {
- height: initial;
- padding: 5px 0;
- }
-
- &--without-height {
- height: initial;
- }
-}
-
-.settings__content-item-col {
- max-width: 300px;
- display: flex;
- flex-direction: column;
-
- @media screen and (max-width: 575px) {
- max-width: 100%;
- width: 100%;
- }
-}
-
-.settings__content-description {
- font-size: 14px;
- color: $dusty-gray;
- padding-top: 5px;
-}
-
-.settings__input {
- padding-left: 10px;
- font-size: 14px;
- height: 40px;
- border: 1px solid $alto;
-}
-
-.settings__input::-webkit-input-placeholder {
- font-weight: 100;
- color: $dusty-gray;
-}
-
-.settings__input::-moz-placeholder {
- font-weight: 100;
- color: $dusty-gray;
-}
-
-.settings__input:-ms-input-placeholder {
- font-weight: 100;
- color: $dusty-gray;
-}
-
-.settings__input:-moz-placeholder {
- font-weight: 100;
- color: $dusty-gray;
-}
-
-.settings__provider-wrapper {
- font-size: 16px;
- border: 1px solid $alto;
- border-radius: 2px;
- padding: 15px;
- background-color: $white;
- display: flex;
- align-items: center;
- justify-content: flex-start;
-}
-
-.settings__provider-icon {
- height: 10px;
- width: 10px;
- margin-right: 10px;
- border-radius: 10px;
-}
-
-.settings__rpc-save-button {
- align-self: flex-end;
- padding: 5px;
- text-transform: uppercase;
- color: $dusty-gray;
- cursor: pointer;
-}
-
-.settings__button--red {
- border-color: lighten($monzo, 10%);
- color: $monzo;
-
- &:active {
- background: lighten($monzo, 55%);
- border-color: $monzo;
- }
-
- &:hover {
- border-color: $monzo;
- }
-}
-
-.settings__button--orange {
- border-color: lighten($ecstasy, 20%);
- color: $ecstasy;
-
- &:active {
- background: lighten($ecstasy, 40%);
- border-color: $ecstasy;
- }
-
- &:hover {
- border-color: $ecstasy;
- }
-}
-
-.settings__info-logo-wrapper {
- height: 80px;
- margin-bottom: 20px;
-}
-
-.settings__info-logo {
- max-height: 100%;
- max-width: 100%;
-}
-
-.settings__info-item {
- padding: 10px 0;
-}
-
-.settings__info-link-header {
- padding-bottom: 15px;
-
- @media screen and (max-width: 575px) {
- padding-bottom: 5px;
- }
-}
-
-.settings__info-link-item {
- padding: 15px 0;
-
- @media screen and (max-width: 575px) {
- padding: 5px 0;
- }
-}
-
-.settings__info-version-number {
- padding-top: 5px;
- font-size: 13px;
- color: $dusty-gray;
-}
-
-.settings__info-about {
- color: $dusty-gray;
- margin-bottom: 15px;
-}
-
-.settings__info-link {
- color: $curious-blue;
-}
-
-.settings__info-separator {
- margin: 15px 0;
- width: 80px;
- border-color: $alto;
- border: none;
- height: 1px;
- background-color: $alto;
- color: $alto;
-}
diff --git a/ui/app/helpers/conversions.util.js b/ui/app/helpers/conversions.util.js
index 5204faa1f..20ef9e35b 100644
--- a/ui/app/helpers/conversions.util.js
+++ b/ui/app/helpers/conversions.util.js
@@ -1,6 +1,11 @@
+import ethUtil from 'ethereumjs-util'
import { conversionUtil } from '../conversion-util'
import { ETH, GWEI, WEI } from '../constants/common'
+export function bnToHex (inputBn) {
+ return ethUtil.addHexPrefix(inputBn.toString(16))
+}
+
export function hexToDecimal (hexValue) {
return conversionUtil(hexValue, {
fromNumericBase: 'hex',
@@ -8,6 +13,13 @@ export function hexToDecimal (hexValue) {
})
}
+export function decimalToHex (decimal) {
+ return conversionUtil(decimal, {
+ fromNumericBase: 'dec',
+ toNumericBase: 'hex',
+ })
+}
+
export function getEthConversionFromWeiHex ({ value, conversionRate, numberOfDecimals = 6 }) {
const denominations = [ETH, GWEI, WEI]
diff --git a/ui/app/helpers/tests/transactions.util.test.js b/ui/app/helpers/tests/transactions.util.test.js
new file mode 100644
index 000000000..103a84a8c
--- /dev/null
+++ b/ui/app/helpers/tests/transactions.util.test.js
@@ -0,0 +1,22 @@
+import * as utils from '../transactions.util'
+import assert from 'assert'
+
+describe('Transactions utils', () => {
+ describe('getTokenData', () => {
+ it('should return token data', () => {
+ const tokenData = utils.getTokenData('0xa9059cbb00000000000000000000000050a9d56c2b8ba9a5c7f2c08c3d26e0499f23a7060000000000000000000000000000000000000000000000000000000000004e20')
+ assert.ok(tokenData)
+ const { name, params } = tokenData
+ assert.equal(name, 'transfer')
+ const [to, value] = params
+ assert.equal(to.name, '_to')
+ assert.equal(to.type, 'address')
+ assert.equal(value.name, '_value')
+ assert.equal(value.type, 'uint256')
+ })
+
+ it('should not throw errors when called without arguments', () => {
+ assert.doesNotThrow(() => utils.getTokenData())
+ })
+ })
+})
diff --git a/ui/app/helpers/transactions.util.js b/ui/app/helpers/transactions.util.js
index 0e1a6ca37..8b87bb538 100644
--- a/ui/app/helpers/transactions.util.js
+++ b/ui/app/helpers/transactions.util.js
@@ -14,19 +14,20 @@ import {
TRANSFER_FROM_ACTION_KEY,
SIGNATURE_REQUEST_KEY,
UNKNOWN_FUNCTION_KEY,
+ CANCEL_ATTEMPT_ACTION_KEY,
} from '../constants/transactions'
import { addCurrencies } from '../conversion-util'
abiDecoder.addABI(abi)
-export function getTokenData (data = {}) {
+export function getTokenData (data = '') {
return abiDecoder.decodeMethod(data)
}
const registry = new MethodRegistry({ provider: global.ethereumProvider })
-export async function getMethodData (data = {}) {
+export async function getMethodData (data = '') {
const prefixedData = ethUtil.addHexPrefix(data)
const fourBytePrefix = prefixedData.slice(0, 10)
const sig = await registry.lookup(fourBytePrefix)
@@ -44,7 +45,11 @@ export function isConfirmDeployContract (txData = {}) {
}
export async function getTransactionActionKey (transaction, methodData) {
- const { txParams: { data, to } = {}, msgParams } = transaction
+ const { txParams: { data, to } = {}, msgParams, type } = transaction
+
+ if (type === 'cancel') {
+ return CANCEL_ATTEMPT_ACTION_KEY
+ }
if (msgParams) {
return SIGNATURE_REQUEST_KEY
diff --git a/ui/app/higher-order-components/with-modal-props/index.js b/ui/app/higher-order-components/with-modal-props/index.js
new file mode 100644
index 000000000..e476b51d2
--- /dev/null
+++ b/ui/app/higher-order-components/with-modal-props/index.js
@@ -0,0 +1 @@
+export { default } from './with-modal-props'
diff --git a/ui/app/higher-order-components/with-modal-props/tests/with-modal-props.test.js b/ui/app/higher-order-components/with-modal-props/tests/with-modal-props.test.js
new file mode 100644
index 000000000..654e7062a
--- /dev/null
+++ b/ui/app/higher-order-components/with-modal-props/tests/with-modal-props.test.js
@@ -0,0 +1,43 @@
+
+import assert from 'assert'
+import configureMockStore from 'redux-mock-store'
+import { mount } from 'enzyme'
+import React from 'react'
+import withModalProps from '../with-modal-props'
+
+const mockState = {
+ appState: {
+ modal: {
+ modalState: {
+ props: {
+ prop1: 'prop1',
+ prop2: 2,
+ prop3: true,
+ },
+ },
+ },
+ },
+}
+
+describe('withModalProps', () => {
+ it('should return a component wrapped with modal state props', () => {
+ const TestComponent = props => (
+ <div className="test">Testing</div>
+ )
+ const WrappedComponent = withModalProps(TestComponent)
+ const store = configureMockStore()(mockState)
+ const wrapper = mount(
+ <WrappedComponent store={store} />
+ )
+
+ assert.ok(wrapper)
+ const testComponent = wrapper.find(TestComponent).at(0)
+ assert.equal(testComponent.length, 1)
+ assert.equal(testComponent.find('.test').text(), 'Testing')
+ const testComponentProps = testComponent.props()
+ assert.equal(testComponentProps.prop1, 'prop1')
+ assert.equal(testComponentProps.prop2, 2)
+ assert.equal(testComponentProps.prop3, true)
+ assert.equal(typeof testComponentProps.hideModal, 'function')
+ })
+})
diff --git a/ui/app/higher-order-components/with-modal-props/with-modal-props.js b/ui/app/higher-order-components/with-modal-props/with-modal-props.js
new file mode 100644
index 000000000..02f3855af
--- /dev/null
+++ b/ui/app/higher-order-components/with-modal-props/with-modal-props.js
@@ -0,0 +1,21 @@
+import { connect } from 'react-redux'
+import { hideModal } from '../../actions'
+
+const mapStateToProps = state => {
+ const { appState } = state
+ const { props: modalProps } = appState.modal.modalState
+
+ return {
+ ...modalProps,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ hideModal: () => dispatch(hideModal()),
+ }
+}
+
+export default function withModalProps (Component) {
+ return connect(mapStateToProps, mapDispatchToProps)(Component)
+}