aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/components
diff options
context:
space:
mode:
authorAlexander Tseung <alextsg@gmail.com>2018-06-23 14:52:45 +0800
committerAlexander Tseung <alextsg@gmail.com>2018-07-07 07:27:08 +0800
commitea9d51e427b8e607e612a01629bebf153e516ad9 (patch)
tree6363cd72b7517442c718901cc8b8ed023d134081 /ui/app/components
parentb4aaf30d6fe829f18dea68a5e6cc321b9fb00d4e (diff)
downloadtangerine-wallet-browser-ea9d51e427b8e607e612a01629bebf153e516ad9.tar
tangerine-wallet-browser-ea9d51e427b8e607e612a01629bebf153e516ad9.tar.gz
tangerine-wallet-browser-ea9d51e427b8e607e612a01629bebf153e516ad9.tar.bz2
tangerine-wallet-browser-ea9d51e427b8e607e612a01629bebf153e516ad9.tar.lz
tangerine-wallet-browser-ea9d51e427b8e607e612a01629bebf153e516ad9.tar.xz
tangerine-wallet-browser-ea9d51e427b8e607e612a01629bebf153e516ad9.tar.zst
tangerine-wallet-browser-ea9d51e427b8e607e612a01629bebf153e516ad9.zip
Refactor and redesign confirm transaction views
Diffstat (limited to 'ui/app/components')
-rw-r--r--ui/app/components/button/button.component.js21
-rw-r--r--ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js52
-rw-r--r--ui/app/components/confirm-page-container/confirm-detail-row/index.js1
-rw-r--r--ui/app/components/confirm-page-container/confirm-detail-row/index.scss43
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js100
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js20
-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/confirm-page-container-error/index.scss17
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js56
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.js1
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss54
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/confirm-page-container-warning.component.js22
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.js1
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.scss18
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/index.js4
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/index.scss66
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js63
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-header/index.js1
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-header/index.scss27
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container.component.js116
-rw-r--r--ui/app/components/confirm-page-container/index.js8
-rw-r--r--ui/app/components/confirm-page-container/index.scss5
-rw-r--r--ui/app/components/dropdowns/components/network-dropdown-icon.js3
-rw-r--r--ui/app/components/index.scss10
-rw-r--r--ui/app/components/input-number.js14
-rw-r--r--ui/app/components/modals/customize-gas/customize-gas.component.js140
-rw-r--r--ui/app/components/modals/customize-gas/customize-gas.container.js22
-rw-r--r--ui/app/components/modals/customize-gas/customize-gas.util.js34
-rw-r--r--ui/app/components/modals/customize-gas/index.js1
-rw-r--r--ui/app/components/modals/customize-gas/index.scss110
-rw-r--r--ui/app/components/modals/index.scss2
-rw-r--r--ui/app/components/modals/modal.js28
-rw-r--r--ui/app/components/network-display.js56
-rw-r--r--ui/app/components/network-display/index.js2
-rw-r--r--ui/app/components/network-display/index.scss54
-rw-r--r--ui/app/components/network-display/network-display.component.js69
-rw-r--r--ui/app/components/network-display/network-display.container.js11
-rw-r--r--ui/app/components/page-container/index.js3
-rw-r--r--ui/app/components/page-container/index.scss186
-rw-r--r--ui/app/components/page-container/page-container-footer/page-container-footer.component.js10
-rw-r--r--ui/app/components/page-container/page-container-header.component.js35
-rw-r--r--ui/app/components/page-container/page-container-header/page-container-header.component.js33
-rw-r--r--ui/app/components/pages/confirm-approve/confirm-approve.component.js30
-rw-r--r--ui/app/components/pages/confirm-approve/confirm-approve.container.js28
-rw-r--r--ui/app/components/pages/confirm-approve/index.js1
-rw-r--r--ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.component.js64
-rw-r--r--ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.container.js12
-rw-r--r--ui/app/components/pages/confirm-deploy-contract/index.js1
-rw-r--r--ui/app/components/pages/confirm-send-ether/confirm-send-ether.component.js31
-rw-r--r--ui/app/components/pages/confirm-send-ether/confirm-send-ether.container.js37
-rw-r--r--ui/app/components/pages/confirm-send-ether/index.js1
-rw-r--r--ui/app/components/pages/confirm-send-token/confirm-send-token.component.js39
-rw-r--r--ui/app/components/pages/confirm-send-token/confirm-send-token.container.js72
-rw-r--r--ui/app/components/pages/confirm-send-token/index.js1
-rw-r--r--ui/app/components/pages/confirm-send-token/index.scss19
-rw-r--r--ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js382
-rw-r--r--ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js95
-rw-r--r--ui/app/components/pages/confirm-transaction-base/index.js1
-rw-r--r--ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js71
-rw-r--r--ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js2
-rw-r--r--ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.container.js11
-rw-r--r--ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.util.js12
-rw-r--r--ui/app/components/pages/confirm-transaction-switch/index.js2
-rw-r--r--ui/app/components/pages/confirm-transaction/confirm-transaction.component.js59
-rw-r--r--ui/app/components/pages/confirm-transaction/confirm-transaction.container.js19
-rw-r--r--ui/app/components/pages/confirm-transaction/index.js2
-rw-r--r--ui/app/components/pages/home.js94
-rw-r--r--ui/app/components/pages/index.scss2
-rw-r--r--ui/app/components/send_/send-footer/send-footer.component.js6
-rw-r--r--ui/app/components/send_/send-footer/send-footer.container.js2
-rw-r--r--ui/app/components/send_/send.utils.js8
-rw-r--r--ui/app/components/sender-to-recipient.js72
-rw-r--r--ui/app/components/sender-to-recipient/index.js1
-rw-r--r--ui/app/components/sender-to-recipient/index.scss74
-rw-r--r--ui/app/components/sender-to-recipient/sender-to-recipient.component.js117
-rw-r--r--ui/app/components/tabs/index.js3
-rw-r--r--ui/app/components/tabs/index.scss11
-rw-r--r--ui/app/components/tabs/tab/index.js2
-rw-r--r--ui/app/components/tabs/tab/index.scss15
-rw-r--r--ui/app/components/tabs/tab/tab.component.js31
-rw-r--r--ui/app/components/tabs/tabs.component.js63
-rw-r--r--ui/app/components/tooltip-v2.js4
82 files changed, 2722 insertions, 295 deletions
diff --git a/ui/app/components/button/button.component.js b/ui/app/components/button/button.component.js
index e8e798445..1e0ef1b64 100644
--- a/ui/app/components/button/button.component.js
+++ b/ui/app/components/button/button.component.js
@@ -5,15 +5,24 @@ import classnames from 'classnames'
const CLASSNAME_DEFAULT = 'btn-default'
const CLASSNAME_PRIMARY = 'btn-primary'
const CLASSNAME_SECONDARY = 'btn-secondary'
+const CLASSNAME_CONFIRM = 'btn-confirm'
const CLASSNAME_LARGE = 'btn--large'
const typeHash = {
default: CLASSNAME_DEFAULT,
primary: CLASSNAME_PRIMARY,
secondary: CLASSNAME_SECONDARY,
+ confirm: CLASSNAME_CONFIRM,
}
-class Button extends Component {
+export default class Button extends Component {
+ static propTypes = {
+ type: PropTypes.string,
+ large: PropTypes.bool,
+ className: PropTypes.string,
+ children: PropTypes.string,
+ }
+
render () {
const { type, large, className, ...buttonProps } = this.props
@@ -31,13 +40,3 @@ class Button extends Component {
)
}
}
-
-Button.propTypes = {
- type: PropTypes.string,
- large: PropTypes.bool,
- className: PropTypes.string,
- children: PropTypes.string,
-}
-
-export default Button
-
diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js b/ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js
new file mode 100644
index 000000000..631cf5803
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js
@@ -0,0 +1,52 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+
+const ConfirmDetailRow = props => {
+ const {
+ label,
+ fiatFee,
+ ethFee,
+ onHeaderClick,
+ fiatFeeColor,
+ headerText,
+ headerTextClassName,
+ } = props
+
+ return (
+ <div className="confirm-detail-row">
+ <div className="confirm-detail-row__label">
+ { label }
+ </div>
+ <div className="confirm-detail-row__details">
+ <div
+ className={classnames('confirm-detail-row__header-text', headerTextClassName)}
+ onClick={() => onHeaderClick && onHeaderClick()}
+ >
+ { headerText }
+ </div>
+ <div
+ className="confirm-detail-row__fiat"
+ style={{ color: fiatFeeColor }}
+ >
+ { fiatFee }
+ </div>
+ <div className="confirm-detail-row__eth">
+ { `\u2666 ${ethFee}` }
+ </div>
+ </div>
+ </div>
+ )
+}
+
+ConfirmDetailRow.propTypes = {
+ label: PropTypes.string,
+ fiatFee: PropTypes.string,
+ ethFee: PropTypes.string,
+ fiatFeeColor: PropTypes.string,
+ onHeaderClick: PropTypes.func,
+ headerText: PropTypes.string,
+ headerTextClassName: PropTypes.string,
+}
+
+export default ConfirmDetailRow
diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/index.js b/ui/app/components/confirm-page-container/confirm-detail-row/index.js
new file mode 100644
index 000000000..056afff04
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-detail-row/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-detail-row.component'
diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/index.scss b/ui/app/components/confirm-page-container/confirm-detail-row/index.scss
new file mode 100644
index 000000000..84d0d56ed
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-detail-row/index.scss
@@ -0,0 +1,43 @@
+.confirm-detail-row {
+ padding: 14px 0;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+
+ &__label {
+ font-size: .75rem;
+ font-weight: 500;
+ color: $scorpion;
+ text-transform: uppercase;
+ }
+
+ &__details {
+ flex: 1;
+ text-align: end;
+ }
+
+ &__fiat {
+ font-size: 1.5rem;
+ }
+
+ &__eth {
+ color: $oslo-gray;
+ }
+
+ &__header-text {
+ font-size: .75rem;
+ text-transform: uppercase;
+ margin-bottom: 6px;
+ color: $scorpion;
+
+ &--edit {
+ color: $curious-blue;
+ cursor: pointer;
+ }
+
+ &--total {
+ font-size: .625rem;
+ }
+ }
+}
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
new file mode 100644
index 000000000..7c7550170
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
@@ -0,0 +1,100 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import { Tabs, Tab } from '../../tabs'
+import {
+ ConfirmPageContainerSummary,
+ ConfirmPageContainerError,
+ ConfirmPageContainerWarning,
+} from './'
+
+export default class ConfirmPageContainerContent extends Component {
+ static propTypes = {
+ action: PropTypes.string,
+ title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ titleComponent: PropTypes.func,
+ subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ hideSubtitle: PropTypes.bool,
+ errorMessage: PropTypes.string,
+ summaryComponent: PropTypes.node,
+ detailsComponent: PropTypes.node,
+ dataComponent: PropTypes.node,
+ identiconAddress: PropTypes.string,
+ nonce: PropTypes.string,
+ warning: PropTypes.string,
+ }
+
+ renderContent () {
+ const { detailsComponent, dataComponent } = this.props
+
+ if (detailsComponent && dataComponent) {
+ return this.renderTabs()
+ } else {
+ return detailsComponent || dataComponent
+ }
+ }
+
+ renderTabs () {
+ const { detailsComponent, dataComponent } = this.props
+
+ return (
+ <Tabs>
+ <Tab name="Details">
+ { detailsComponent }
+ </Tab>
+ <Tab name="Data">
+ { dataComponent }
+ </Tab>
+ </Tabs>
+ )
+ }
+
+ render () {
+ const {
+ action,
+ title,
+ subtitle,
+ hideSubtitle,
+ errorMessage,
+ identiconAddress,
+ nonce,
+ summaryComponent,
+ detailsComponent,
+ dataComponent,
+ warning,
+ } = this.props
+
+ return (
+ <div className="confirm-page-container-content">
+ {
+ warning && (
+ <ConfirmPageContainerWarning warning={warning} />
+ )
+ }
+ {
+ summaryComponent || (
+ <ConfirmPageContainerSummary
+ className={classnames({
+ 'confirm-page-container-summary--border': !detailsComponent || !dataComponent,
+ })}
+ action={action}
+ title={title}
+ subtitle={subtitle}
+ hideSubtitle={hideSubtitle}
+ identiconAddress={identiconAddress}
+ nonce={nonce}
+ />
+ )
+ }
+ { this.renderContent() }
+ {
+ errorMessage && (
+ <div className="confirm-page-container-content__error-container">
+ <ConfirmPageContainerError error={errorMessage} />
+ </div>
+ )
+ }
+ </div>
+ )
+ }
+}
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/confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js
new file mode 100644
index 000000000..dc5b9b935
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js
@@ -0,0 +1,20 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+const ConfirmPageContainerError = props => {
+ return (
+ <div className="confirm-page-container-error">
+ <img
+ src="/images/alert-red.svg"
+ className="confirm-page-container-error__icon"
+ />
+ { `ALERT: ${props.error}` }
+ </div>
+ )
+}
+
+ConfirmPageContainerError.propTypes = {
+ error: PropTypes.string,
+}
+
+export default ConfirmPageContainerError
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
new file mode 100644
index 000000000..4ac95d0e3
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-page-container-error.component'
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.scss b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.scss
new file mode 100644
index 000000000..e99b0f631
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.scss
@@ -0,0 +1,17 @@
+.confirm-page-container-error {
+ height: 32px;
+ border: 1px solid $monzo;
+ color: $monzo;
+ background: lighten($monzo, 56%);
+ border-radius: 4px;
+ font-size: .75rem;
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ padding-left: 16px;
+
+ &__icon {
+ margin-right: 8px;
+ flex: 0 0 auto;
+ }
+}
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
new file mode 100644
index 000000000..3b1ee62c5
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
@@ -0,0 +1,56 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import Identicon from '../../../identicon'
+
+const ConfirmPageContainerSummary = props => {
+ const { action, title, subtitle, hideSubtitle, className, identiconAddress, nonce } = props
+
+ return (
+ <div className={classnames('confirm-page-container-summary', className)}>
+ <div className="confirm-page-container-summary__action-row">
+ <div className="confirm-page-container-summary__action">
+ { action }
+ </div>
+ {
+ nonce && (
+ <div className="confirm-page-container-summary__nonce">
+ { `#${nonce}` }
+ </div>
+ )
+ }
+ </div>
+ <div className="confirm-page-container-summary__title">
+ {
+ identiconAddress && (
+ <Identicon
+ className="confirm-page-container-summary__identicon"
+ diameter={36}
+ address={identiconAddress}
+ />
+ )
+ }
+ <div className="confirm-page-container-summary__title-text">
+ { title }
+ </div>
+ </div>
+ {
+ hideSubtitle || <div className="confirm-page-container-summary__subtitle">
+ { subtitle }
+ </div>
+ }
+ </div>
+ )
+}
+
+ConfirmPageContainerSummary.propTypes = {
+ action: PropTypes.string,
+ title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ hideSubtitle: PropTypes.bool,
+ className: PropTypes.string,
+ identiconAddress: PropTypes.string,
+ nonce: PropTypes.string,
+}
+
+export default ConfirmPageContainerSummary
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.js b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.js
new file mode 100644
index 000000000..ed1b28cf2
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-page-container-summary.component'
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss
new file mode 100644
index 000000000..7f0f5d37a
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss
@@ -0,0 +1,54 @@
+.confirm-page-container-summary {
+ padding: 16px 24px 0;
+ background-color: #f9fafa;
+ height: 133px;
+ box-sizing: border-box;
+
+ &__action-row {
+ display: flex;
+ justify-content: space-between;
+ }
+
+ &__action {
+ text-transform: uppercase;
+ color: $oslo-gray;
+ font-size: .75rem;
+ padding: 3px 8px;
+ border: 1px solid $oslo-gray;
+ border-radius: 4px;
+ display: inline-block;
+ }
+
+ &__nonce {
+ color: $oslo-gray;
+ }
+
+ &__title {
+ padding: 4px 0;
+ display: flex;
+ align-items: center;
+ }
+
+ &__identicon {
+ flex: 0 0 auto;
+ margin-right: 8px;
+ }
+
+ &__title-text {
+ font-size: 2.25rem;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &__subtitle {
+ color: $oslo-gray;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &--border {
+ border-bottom: 1px solid $geyser;
+ }
+}
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/confirm-page-container-warning.component.js b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/confirm-page-container-warning.component.js
new file mode 100644
index 000000000..79901c8fc
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/confirm-page-container-warning.component.js
@@ -0,0 +1,22 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+const ConfirmPageContainerWarning = props => {
+ return (
+ <div className="confirm-page-container-warning">
+ <img
+ className="confirm-page-container-warning__icon"
+ src="/images/alert.svg"
+ />
+ <div className="confirm-page-container-warning__warning">
+ { props.warning }
+ </div>
+ </div>
+ )
+}
+
+ConfirmPageContainerWarning.propTypes = {
+ warning: PropTypes.string,
+}
+
+export default ConfirmPageContainerWarning
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.js b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.js
new file mode 100644
index 000000000..6e48bd144
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-page-container-warning.component'
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.scss b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.scss
new file mode 100644
index 000000000..189a5c5c6
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-warning/index.scss
@@ -0,0 +1,18 @@
+.confirm-page-container-warning {
+ background-color: #fffcdb;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-bottom: 1px solid $geyser;
+ padding: 12px 24px;
+
+ &__icon {
+ flex: 0 0 auto;
+ margin-right: 16px;
+ }
+
+ &__warning {
+ font-size: .75rem;
+ color: $oslo-gray;
+ }
+}
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
new file mode 100644
index 000000000..1469dd438
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/index.js
@@ -0,0 +1,4 @@
+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
new file mode 100644
index 000000000..39797a43f
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss
@@ -0,0 +1,66 @@
+@import './confirm-page-container-error/index';
+
+@import './confirm-page-container-warning/index';
+
+@import './confirm-page-container-summary/index';
+
+.confirm-page-container-content {
+ overflow-y: auto;
+ flex: 1;
+
+ &__error-container {
+ padding: 0 16px 16px 16px;
+ }
+
+ &__details {
+ box-sizing: border-box;
+ padding: 0 24px;
+ }
+
+ &__data {
+ padding: 16px;
+ color: $oslo-gray;
+ }
+
+ &__data-box {
+ background-color: #f9fafa;
+ padding: 12px;
+ font-size: .75rem;
+ margin-bottom: 16px;
+ word-wrap: break-word;
+ max-height: 200px;
+ overflow-y: auto;
+
+ &-label {
+ text-transform: uppercase;
+ padding: 8px 0 12px;
+ font-size: 12px;
+ }
+ }
+
+ &__data-field {
+ display: flex;
+ flex-direction: row;
+
+ &-label {
+ font-weight: 500;
+ padding-right: 16px;
+ }
+
+ &:not(:last-child) {
+ margin-bottom: 5px;
+ }
+ }
+
+ &__gas-fee {
+ border-bottom: 1px solid $geyser;
+ }
+
+ &__function-type {
+ font-size: .875rem;
+ font-weight: 500;
+ text-transform: capitalize;
+ color: $black;
+ padding-left: 5px;
+ }
+}
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js b/ui/app/components/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js
new file mode 100644
index 000000000..e6fe8f82c
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js
@@ -0,0 +1,63 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import {
+ ENVIRONMENT_TYPE_POPUP,
+ ENVIRONMENT_TYPE_NOTIFICATION,
+} from '../../../../../app/scripts/lib/enums'
+import NetworkDisplay from '../../network-display'
+
+export default class ConfirmPageContainer extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ showEdit: PropTypes.bool,
+ onEdit: PropTypes.func,
+ children: PropTypes.node,
+ }
+
+ renderTop () {
+ const { onEdit, showEdit } = this.props
+ const windowType = window.METAMASK_UI_TYPE
+ const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
+ windowType !== ENVIRONMENT_TYPE_POPUP
+
+ if (!showEdit && isFullScreen) {
+ return null
+ }
+
+ return (
+ <div className="confirm-page-container-header__row">
+ <div
+ className="confirm-page-container-header__back-button-container"
+ style={{
+ visibility: showEdit ? 'initial' : 'hidden',
+ }}
+ >
+ <img
+ src="/images/caret-left.svg"
+ />
+ <span
+ className="confirm-page-container-header__back-button"
+ onClick={() => onEdit()}
+ >
+ { this.context.t('edit') }
+ </span>
+ </div>
+ { !isFullScreen && <NetworkDisplay /> }
+ </div>
+ )
+ }
+
+ render () {
+ const { children } = this.props
+
+ return (
+ <div className="confirm-page-container-header">
+ { this.renderTop() }
+ { children }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-header/index.js b/ui/app/components/confirm-page-container/confirm-page-container-header/index.js
new file mode 100644
index 000000000..71feb6931
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-header/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-page-container-header.component'
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-header/index.scss b/ui/app/components/confirm-page-container/confirm-page-container-header/index.scss
new file mode 100644
index 000000000..43e1e4427
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-header/index.scss
@@ -0,0 +1,27 @@
+.confirm-page-container-header {
+ display: flex;
+ flex-direction: column;
+ flex: 0 0 auto;
+
+ &__row {
+ display: flex;
+ justify-content: space-between;
+ border-bottom: 1px solid $geyser;
+ padding: 13px 13px 13px 24px;
+ flex: 0 0 auto;
+ }
+
+ &__back-button-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ &__back-button {
+ color: #2f9ae0;
+ font-size: 1rem;
+ cursor: pointer;
+ font-weight: 400;
+ padding-left: 5px;
+ }
+}
diff --git a/ui/app/components/confirm-page-container/confirm-page-container.component.js b/ui/app/components/confirm-page-container/confirm-page-container.component.js
new file mode 100644
index 000000000..d5247f83b
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container.component.js
@@ -0,0 +1,116 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import SenderToRecipient from '../sender-to-recipient'
+import { PageContainerFooter } from '../page-container'
+import { ConfirmPageContainerHeader, ConfirmPageContainerContent } from './'
+
+export default class ConfirmPageContainer extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ showEdit: PropTypes.bool,
+ onEdit: PropTypes.func,
+ // Sender to Recipient
+ fromName: PropTypes.string,
+ fromAddress: PropTypes.string,
+ toName: PropTypes.string,
+ toAddress: PropTypes.string,
+
+ valid: PropTypes.bool,
+ errorMessage: PropTypes.string,
+ // Header
+ action: PropTypes.string,
+ title: PropTypes.string,
+ titleComponent: PropTypes.func,
+ subtitle: PropTypes.string,
+ hideSubtitle: PropTypes.bool,
+ // Content
+ summaryComponent: PropTypes.node,
+ contentComponent: PropTypes.node,
+ fiatTransactionAmount: PropTypes.string,
+ fiatTransactionFee: PropTypes.string,
+ fiatTransactionTotal: PropTypes.string,
+ ethTransactionAmount: PropTypes.string,
+ ethTransactionFee: PropTypes.string,
+ ethTransactionTotal: PropTypes.string,
+ onEditGas: PropTypes.func,
+ detailsComponent: PropTypes.node,
+ dataComponent: PropTypes.node,
+ identiconAddress: PropTypes.string,
+ nonce: PropTypes.string,
+ warning: PropTypes.string,
+ // Footer
+ onCancel: PropTypes.func,
+ onSubmit: PropTypes.func,
+ }
+
+ render () {
+ const {
+ showEdit,
+ onEdit,
+ fromName,
+ fromAddress,
+ toName,
+ toAddress,
+ valid,
+ errorMessage,
+ contentComponent,
+ action,
+ title,
+ titleComponent,
+ subtitle,
+ hideSubtitle,
+ summaryComponent,
+ detailsComponent,
+ dataComponent,
+ onCancel,
+ onSubmit,
+ identiconAddress,
+ nonce,
+ warning,
+ } = this.props
+
+ return (
+ <div className="page-container">
+ <ConfirmPageContainerHeader
+ showEdit={showEdit}
+ onEdit={() => onEdit()}
+ >
+ <SenderToRecipient
+ senderName={fromName}
+ senderAddress={fromAddress}
+ recipientName={toName}
+ recipientAddress={toAddress}
+ />
+ </ConfirmPageContainerHeader>
+ {
+ contentComponent || (
+ <ConfirmPageContainerContent
+ action={action}
+ title={title}
+ titleComponent={titleComponent}
+ subtitle={subtitle}
+ hideSubtitle={hideSubtitle}
+ summaryComponent={summaryComponent}
+ detailsComponent={detailsComponent}
+ dataComponent={dataComponent}
+ errorMessage={errorMessage}
+ identiconAddress={identiconAddress}
+ nonce={nonce}
+ warning={warning}
+ />
+ )
+ }
+ <PageContainerFooter
+ onCancel={() => onCancel()}
+ onSubmit={() => onSubmit()}
+ submitText={this.context.t('confirm')}
+ submitButtonType="confirm"
+ disabled={!valid}
+ />
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/confirm-page-container/index.js b/ui/app/components/confirm-page-container/index.js
new file mode 100644
index 000000000..ee88aa5d3
--- /dev/null
+++ b/ui/app/components/confirm-page-container/index.js
@@ -0,0 +1,8 @@
+export { default } from './confirm-page-container.component'
+export { default as ConfirmPageContainerHeader } from './confirm-page-container-header'
+export { default as ConfirmDetailRow } from './confirm-detail-row'
+export {
+ default as ConfirmPageContainerContent,
+ ConfirmPageContainerSummary,
+ ConfirmPageContainerError,
+} from './confirm-page-container-content'
diff --git a/ui/app/components/confirm-page-container/index.scss b/ui/app/components/confirm-page-container/index.scss
new file mode 100644
index 000000000..af7a5b555
--- /dev/null
+++ b/ui/app/components/confirm-page-container/index.scss
@@ -0,0 +1,5 @@
+@import './confirm-page-container-content/index';
+
+@import './confirm-page-container-header/index';
+
+@import './confirm-detail-row/index';
diff --git a/ui/app/components/dropdowns/components/network-dropdown-icon.js b/ui/app/components/dropdowns/components/network-dropdown-icon.js
index 7e94e0af5..a45da4c10 100644
--- a/ui/app/components/dropdowns/components/network-dropdown-icon.js
+++ b/ui/app/components/dropdowns/components/network-dropdown-icon.js
@@ -15,6 +15,7 @@ NetworkDropdownIcon.prototype.render = function () {
backgroundColor,
isSelected,
innerBorder = 'none',
+ diameter = '12',
} = this.props
return h(`.menu-icon-circle${isSelected ? '--active' : ''}`, {},
@@ -22,6 +23,8 @@ NetworkDropdownIcon.prototype.render = function () {
style: {
background: backgroundColor,
border: innerBorder,
+ height: `${diameter}px`,
+ width: `${diameter}px`,
},
})
)
diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss
index 351640f6e..32f0e90e4 100644
--- a/ui/app/components/index.scss
+++ b/ui/app/components/index.scss
@@ -4,6 +4,16 @@
@import './info-box/index';
+@import './network-display/index';
+
+@import './confirm-page-container/index';
+
+@import './page-container/index';
+
@import './pages/index';
@import './modals/index';
+
+@import './sender-to-recipient/index';
+
+@import './tabs/index';
diff --git a/ui/app/components/input-number.js b/ui/app/components/input-number.js
index 59c6842ef..01f751b69 100644
--- a/ui/app/components/input-number.js
+++ b/ui/app/components/input-number.js
@@ -2,10 +2,8 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const {
- addCurrencies,
conversionGTE,
conversionLTE,
- subtractCurrencies,
} = require('../conversion-util')
module.exports = InputNumber
@@ -51,7 +49,11 @@ InputNumber.prototype.setValue = function (newValue) {
}
InputNumber.prototype.render = function () {
+<<<<<<< HEAD
const { unitLabel, step = 1, placeholder, value } = this.props
+=======
+ const { unitLabel, step = 1, placeholder, value = 0, min = -1, max = Infinity } = this.props
+>>>>>>> Refactor and redesign confirm transaction views
return h('div.customize-gas-input-wrapper', {}, [
h('input', {
@@ -67,11 +69,19 @@ InputNumber.prototype.render = function () {
h('span.gas-tooltip-input-detail', {}, [unitLabel]),
h('div.gas-tooltip-input-arrows', {}, [
h('i.fa.fa-angle-up', {
+<<<<<<< HEAD
onClick: () => this.setValue(addCurrencies(value, step, { toNumericBase: 'dec' })),
}),
h('i.fa.fa-angle-down', {
style: { cursor: 'pointer' },
onClick: () => this.setValue(subtractCurrencies(value, step, { toNumericBase: 'dec' })),
+=======
+ onClick: () => this.setValue(Math.min(+value + step, max)),
+ }),
+ h('i.fa.fa-angle-down', {
+ style: { cursor: 'pointer' },
+ onClick: () => this.setValue(Math.max(+value - step, min)),
+>>>>>>> Refactor and redesign confirm transaction views
}),
]),
])
diff --git a/ui/app/components/modals/customize-gas/customize-gas.component.js b/ui/app/components/modals/customize-gas/customize-gas.component.js
new file mode 100644
index 000000000..cfaafd2b9
--- /dev/null
+++ b/ui/app/components/modals/customize-gas/customize-gas.component.js
@@ -0,0 +1,140 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import GasModalCard from '../../customize-gas-modal/gas-modal-card'
+import { MIN_GAS_PRICE_GWEI } from '../../send_/send.constants'
+
+import {
+ getDecimalGasLimit,
+ getDecimalGasPrice,
+ getPrefixedHexGasLimit,
+ getPrefixedHexGasPrice,
+} from './customize-gas.util'
+
+export default class CustomizeGas extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ txData: PropTypes.object.isRequired,
+ hideModal: PropTypes.func,
+ validate: PropTypes.func,
+ onSubmit: PropTypes.func,
+ }
+
+ state = {
+ gasPrice: 0,
+ gasLimit: 0,
+ originalGasPrice: 0,
+ originalGasLimit: 0,
+ }
+
+ componentDidMount () {
+ const { txData = {} } = this.props
+ const { txParams: { gas: hexGasLimit, gasPrice: hexGasPrice } = {} } = txData
+
+ const gasLimit = getDecimalGasLimit(hexGasLimit)
+ const gasPrice = getDecimalGasPrice(hexGasPrice)
+
+ this.setState({
+ gasPrice,
+ gasLimit,
+ originalGasPrice: gasPrice,
+ originalGasLimit: gasLimit,
+ })
+ }
+
+ handleRevert () {
+ const { originalGasPrice, originalGasLimit } = this.state
+
+ this.setState({
+ gasPrice: originalGasPrice,
+ gasLimit: originalGasLimit,
+ })
+ }
+
+ handleSave () {
+ const { onSubmit, hideModal } = this.props
+ const { gasLimit, gasPrice } = this.state
+ const prefixedHexGasPrice = getPrefixedHexGasPrice(gasPrice)
+ const prefixedHexGasLimit = getPrefixedHexGasLimit(gasLimit)
+
+ Promise.resolve(onSubmit({ gasPrice: prefixedHexGasPrice, gasLimit: prefixedHexGasLimit }))
+ .then(() => hideModal())
+ }
+
+ validate () {
+ const { gasLimit, gasPrice } = this.state
+ return this.props.validate({
+ gasPrice: getPrefixedHexGasPrice(gasPrice),
+ gasLimit: getPrefixedHexGasLimit(gasLimit),
+ })
+ }
+
+ render () {
+ const { t } = this.context
+ const { hideModal } = this.props
+ const { gasPrice, gasLimit } = this.state
+ const { valid, errorMessage } = this.validate()
+
+ return (
+ <div className="customize-gas">
+ <div className="customize-gas__content">
+ <div className="customize-gas__header">
+ <div className="customize-gas__title">
+ { this.context.t('customGas') }
+ </div>
+ <div
+ className="customize-gas__close"
+ onClick={() => hideModal()}
+ />
+ </div>
+ <div className="customize-gas__body">
+ <GasModalCard
+ value={gasPrice}
+ min={MIN_GAS_PRICE_GWEI}
+ step={1}
+ onChange={value => this.setState({ gasPrice: value })}
+ title={t('gasPrice')}
+ copy={t('gasPriceCalculation')}
+ />
+ <GasModalCard
+ value={gasLimit}
+ min={1}
+ step={1}
+ onChange={value => this.setState({ gasLimit: value })}
+ title={t('gasLimit')}
+ copy={t('gasLimitCalculation')}
+ />
+ </div>
+ <div className="customize-gas__footer">
+ { !valid && <div className="customize-gas__error-message">{ errorMessage }</div> }
+ <div
+ className="customize-gas__revert"
+ onClick={() => this.handleRevert()}
+ >
+ { t('revert') }
+ </div>
+ <div className="customize-gas__buttons">
+ <button
+ className="btn-default customize-gas__cancel"
+ onClick={() => hideModal()}
+ style={{ marginRight: '10px' }}
+ >
+ { t('cancel') }
+ </button>
+ <button
+ className="btn-primary customize-gas__save"
+ onClick={() => this.handleSave()}
+ style={{ marginRight: '10px' }}
+ disabled={!valid}
+ >
+ { t('save') }
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/modals/customize-gas/customize-gas.container.js b/ui/app/components/modals/customize-gas/customize-gas.container.js
new file mode 100644
index 000000000..46a799795
--- /dev/null
+++ b/ui/app/components/modals/customize-gas/customize-gas.container.js
@@ -0,0 +1,22 @@
+import { connect } from 'react-redux'
+import CustomizeGas from './customize-gas.component'
+import { hideModal } from '../../../actions'
+
+const mapStateToProps = state => {
+ const { appState: { modal: { modalState: { props } } } } = state
+ const { txData, onSubmit, validate } = props
+
+ return {
+ txData,
+ onSubmit,
+ validate,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ hideModal: () => dispatch(hideModal()),
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(CustomizeGas)
diff --git a/ui/app/components/modals/customize-gas/customize-gas.util.js b/ui/app/components/modals/customize-gas/customize-gas.util.js
new file mode 100644
index 000000000..6ba4a7705
--- /dev/null
+++ b/ui/app/components/modals/customize-gas/customize-gas.util.js
@@ -0,0 +1,34 @@
+import ethUtil from 'ethereumjs-util'
+import { conversionUtil } from '../../../conversion-util'
+
+export function getDecimalGasLimit (hexGasLimit) {
+ return conversionUtil(hexGasLimit, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ })
+}
+
+export function getDecimalGasPrice (hexGasPrice) {
+ return conversionUtil(hexGasPrice, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ fromDenomination: 'WEI',
+ toDenomination: 'GWEI',
+ })
+}
+
+export function getPrefixedHexGasLimit (gasLimit) {
+ return ethUtil.addHexPrefix(conversionUtil(gasLimit, {
+ fromNumericBase: 'dec',
+ toNumericBase: 'hex',
+ }))
+}
+
+export function getPrefixedHexGasPrice (gasPrice) {
+ return ethUtil.addHexPrefix(conversionUtil(gasPrice, {
+ fromNumericBase: 'dec',
+ toNumericBase: 'hex',
+ fromDenomination: 'GWEI',
+ toDenomination: 'WEI',
+ }))
+}
diff --git a/ui/app/components/modals/customize-gas/index.js b/ui/app/components/modals/customize-gas/index.js
new file mode 100644
index 000000000..3a0ab7edc
--- /dev/null
+++ b/ui/app/components/modals/customize-gas/index.js
@@ -0,0 +1 @@
+export { default } from './customize-gas.container'
diff --git a/ui/app/components/modals/customize-gas/index.scss b/ui/app/components/modals/customize-gas/index.scss
new file mode 100644
index 000000000..e10452691
--- /dev/null
+++ b/ui/app/components/modals/customize-gas/index.scss
@@ -0,0 +1,110 @@
+.customize-gas {
+ border: 1px solid #D8D8D8;
+ border-radius: 4px;
+ background-color: #FFFFFF;
+ box-shadow: 0 2px 4px 0 rgba(0,0,0,0.14);
+ font-family: Roboto;
+ display: flex;
+ flex-flow: column;
+
+ @media screen and (max-width: $break-small) {
+ width: 100vw;
+ height: 100vh;
+ }
+
+ &__header {
+ height: 52px;
+ border-bottom: 1px solid $alto;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 22px;
+
+ @media screen and (max-width: $break-small) {
+ flex: 0 0 auto;
+ }
+ }
+
+ &__title {
+ margin-left: 19.25px;
+ }
+
+ &__close::after {
+ content: '\00D7';
+ font-size: 1.8em;
+ color: $dusty-gray;
+ font-family: sans-serif;
+ cursor: pointer;
+ margin-right: 19.25px;
+ }
+
+ &__content {
+ display: flex;
+ flex-flow: column nowrap;
+ height: 100%;
+ }
+
+ &__body {
+ display: flex;
+ margin-bottom: 24px;
+
+ @media screen and (max-width: $break-small) {
+ flex-flow: column;
+ flex: 1 1 auto;
+ }
+ }
+
+ &__footer {
+ height: 75px;
+ border-top: 1px solid $alto;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 22px;
+ position: relative;
+
+ @media screen and (max-width: $break-small) {
+ flex: 0 0 auto;
+ }
+ }
+
+ &__buttons {
+ display: flex;
+ justify-content: space-between;
+ margin-right: 21.25px;
+ }
+
+ &__revert, &__cancel, &__save, &__save__error {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 0 3px;
+ cursor: pointer;
+ }
+
+ &__revert {
+ color: $silver-chalice;
+ font-size: 16px;
+ margin-left: 21.25px;
+ }
+
+ &__cancel, &__save, &__save__error {
+ width: 85.74px;
+ min-width: initial;
+ }
+
+ &__save__error {
+ opacity: 0.5;
+ cursor: auto;
+ }
+
+ &__error-message {
+ display: block;
+ position: absolute;
+ top: 4px;
+ right: 4px;
+ font-size: 12px;
+ line-height: 12px;
+ color: $red;
+ }
+}
diff --git a/ui/app/components/modals/index.scss b/ui/app/components/modals/index.scss
index ad6fe16d3..160911c10 100644
--- a/ui/app/components/modals/index.scss
+++ b/ui/app/components/modals/index.scss
@@ -1,3 +1,5 @@
+@import './customize-gas/index';
+
.modal-container {
width: 100%;
height: 100%;
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
index 85e85597a..973438b6b 100644
--- a/ui/app/components/modals/modal.js
+++ b/ui/app/components/modals/modal.js
@@ -24,6 +24,8 @@ const TransactionConfirmed = require('./transaction-confirmed')
const WelcomeBeta = require('./welcome-beta')
const Notification = require('./notification')
+import ConfirmCustomizeGasModal from './customize-gas'
+
const modalContainerBaseStyle = {
transform: 'translate3d(-50%, 0, 0px)',
border: '1px solid #CCCFD1',
@@ -267,7 +269,31 @@ const MODALS = {
CUSTOMIZE_GAS: {
contents: [
- h(CustomizeGasModal, {}, []),
+ h(CustomizeGasModal),
+ ],
+ mobileModalStyle: {
+ width: '100vw',
+ height: '100vh',
+ top: '0',
+ transform: 'none',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ },
+ laptopModalStyle: {
+ width: '720px',
+ height: '377px',
+ top: '80px',
+ transform: 'none',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ },
+ },
+
+ CONFIRM_CUSTOMIZE_GAS: {
+ contents: [
+ h(ConfirmCustomizeGasModal),
],
mobileModalStyle: {
width: '100vw',
diff --git a/ui/app/components/network-display.js b/ui/app/components/network-display.js
deleted file mode 100644
index 59719d9a4..000000000
--- a/ui/app/components/network-display.js
+++ /dev/null
@@ -1,56 +0,0 @@
-const { Component } = require('react')
-const h = require('react-hyperscript')
-const PropTypes = require('prop-types')
-const connect = require('react-redux').connect
-const NetworkDropdownIcon = require('./dropdowns/components/network-dropdown-icon')
-
-const networkToColorHash = {
- 1: '#038789',
- 3: '#e91550',
- 42: '#690496',
- 4: '#ebb33f',
-}
-
-class NetworkDisplay extends Component {
- renderNetworkIcon () {
- const { network } = this.props
- const networkColor = networkToColorHash[network]
-
- return networkColor
- ? h(NetworkDropdownIcon, { backgroundColor: networkColor })
- : h('i.fa.fa-question-circle.fa-med', {
- style: {
- margin: '0 4px',
- color: 'rgb(125, 128, 130)',
- },
- })
- }
-
- render () {
- const { provider: { type } } = this.props
- return h('.network-display__container', [
- this.renderNetworkIcon(),
- h('.network-name', this.context.t(type)),
- ])
- }
-}
-
-NetworkDisplay.propTypes = {
- network: PropTypes.string,
- provider: PropTypes.object,
- t: PropTypes.func,
-}
-
-const mapStateToProps = ({ metamask: { network, provider } }) => {
- return {
- network,
- provider,
- }
-}
-
-NetworkDisplay.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect(mapStateToProps)(NetworkDisplay)
-
diff --git a/ui/app/components/network-display/index.js b/ui/app/components/network-display/index.js
new file mode 100644
index 000000000..f6878ae5b
--- /dev/null
+++ b/ui/app/components/network-display/index.js
@@ -0,0 +1,2 @@
+import NetworkDisplay from './network-display.container'
+module.exports = NetworkDisplay
diff --git a/ui/app/components/network-display/index.scss b/ui/app/components/network-display/index.scss
new file mode 100644
index 000000000..3bce81aaa
--- /dev/null
+++ b/ui/app/components/network-display/index.scss
@@ -0,0 +1,54 @@
+.network-display {
+ &__container {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ background-color: lighten(rgb(125, 128, 130), 45%);
+ padding: 0 10px;
+ border-radius: 4px;
+ height: 25px;
+
+ &--mainnet {
+ background-color: lighten($blue-lagoon, 45%);
+ }
+
+ &--ropsten {
+ background-color: lighten($crimson, 45%);
+ }
+
+ &--kovan {
+ background-color: lighten($purple, 45%);
+ }
+
+ &--rinkeby {
+ background-color: lighten($tulip-tree, 45%);
+ }
+ }
+
+ &__name {
+ font-size: .875rem;
+ padding-left: 5px;
+ }
+
+ &__icon {
+ height: 10px;
+ width: 10px;
+ border-radius: 10px;
+
+ &--mainnet {
+ background-color: $blue-lagoon;
+ }
+
+ &--ropsten {
+ background-color: $crimson;
+ }
+
+ &--kovan {
+ background-color: $purple;
+ }
+
+ &--rinkeby {
+ background-color: $tulip-tree;
+ }
+ }
+}
diff --git a/ui/app/components/network-display/network-display.component.js b/ui/app/components/network-display/network-display.component.js
new file mode 100644
index 000000000..38626af20
--- /dev/null
+++ b/ui/app/components/network-display/network-display.component.js
@@ -0,0 +1,69 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import {
+ MAINNET_CODE,
+ ROPSTEN_CODE,
+ RINKEYBY_CODE,
+ KOVAN_CODE,
+} from '../../../../app/scripts/controllers/network/enums'
+
+const networkToClassHash = {
+ [MAINNET_CODE]: 'mainnet',
+ [ROPSTEN_CODE]: 'ropsten',
+ [RINKEYBY_CODE]: 'rinkeby',
+ [KOVAN_CODE]: 'kovan',
+}
+
+export default class NetworkDisplay extends Component {
+ static propTypes = {
+ network: PropTypes.string,
+ provider: PropTypes.object,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ renderNetworkIcon () {
+ const { network } = this.props
+ const networkClass = networkToClassHash[network]
+
+ return networkClass
+ ? <div className={`network-display__icon network-display__icon--${networkClass}`} />
+ : <div
+ className="i fa fa-question-circle fa-med"
+ style={{
+ margin: '0 4px',
+ color: 'rgb(125, 128, 130)',
+ }}
+ />
+ }
+
+ render () {
+ const { network, provider: { type } } = this.props
+ const networkClass = networkToClassHash[network]
+
+ return (
+ <div className={classnames(
+ 'network-display__container',
+ networkClass && ('network-display__container--' + networkClass)
+ )}>
+ {
+ networkClass
+ ? <div className={`network-display__icon network-display__icon--${networkClass}`} />
+ : <div
+ className="i fa fa-question-circle fa-med"
+ style={{
+ margin: '0 4px',
+ color: 'rgb(125, 128, 130)',
+ }}
+ />
+ }
+ <div className="network-display__name">
+ { this.context.t(type) }
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/network-display/network-display.container.js b/ui/app/components/network-display/network-display.container.js
new file mode 100644
index 000000000..99a14fff4
--- /dev/null
+++ b/ui/app/components/network-display/network-display.container.js
@@ -0,0 +1,11 @@
+import { connect } from 'react-redux'
+import NetworkDisplay from './network-display.component'
+
+const mapStateToProps = ({ metamask: { network, provider } }) => {
+ return {
+ network,
+ provider,
+ }
+}
+
+export default connect(mapStateToProps)(NetworkDisplay)
diff --git a/ui/app/components/page-container/index.js b/ui/app/components/page-container/index.js
index 415870b37..913b8c9c6 100644
--- a/ui/app/components/page-container/index.js
+++ b/ui/app/components/page-container/index.js
@@ -1 +1,4 @@
+import PageContainerHeader from './page-container-header'
+import PageContainerFooter from './page-container-footer'
export { default } from './page-container.component'
+export { PageContainerHeader, PageContainerFooter }
diff --git a/ui/app/components/page-container/index.scss b/ui/app/components/page-container/index.scss
new file mode 100644
index 000000000..06c3ef709
--- /dev/null
+++ b/ui/app/components/page-container/index.scss
@@ -0,0 +1,186 @@
+.page-container {
+ width: 408px;
+ background-color: $white;
+ box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08);
+ z-index: 25;
+ display: flex;
+ flex-flow: column;
+ border-radius: 8px;
+
+ &__header {
+ display: flex;
+ flex-flow: column;
+ border-bottom: 1px solid $geyser;
+ padding: 16px;
+ flex: 0 0 auto;
+ position: relative;
+
+ &--no-padding-bottom {
+ padding-bottom: 0;
+ }
+ }
+
+ &__header-close {
+ color: $tundora;
+ position: absolute;
+ top: 16px;
+ right: 16px;
+ cursor: pointer;
+ overflow: hidden;
+
+ &::after {
+ content: '\00D7';
+ font-size: 40px;
+ line-height: 20px;
+ }
+ }
+
+ &__header-row {
+ padding-bottom: 10px;
+ display: flex;
+ justify-content: space-between;
+ }
+
+ &__footer {
+ display: flex;
+ flex-flow: row;
+ justify-content: center;
+ border-top: 1px solid $geyser;
+ padding: 16px;
+ flex: 0 0 auto;
+
+ .btn-default,
+ .btn-confirm {
+ font-size: 1rem;
+ }
+ }
+
+ &__footer-button {
+ height: 55px;
+ font-size: 1rem;
+ text-transform: uppercase;
+ margin-right: 16px;
+
+ &:last-of-type {
+ margin-right: 0;
+ }
+ }
+
+ &__back-button {
+ color: #2f9ae0;
+ font-size: 1rem;
+ cursor: pointer;
+ font-weight: 400;
+ }
+
+ &__title {
+ color: $black;
+ font-size: 2rem;
+ font-weight: 500;
+ line-height: 2rem;
+ }
+
+ &__subtitle {
+ padding-top: .5rem;
+ line-height: initial;
+ font-size: .9rem;
+ color: $gray;
+ }
+
+ &__tabs {
+ display: flex;
+ margin-top: 16px;
+ }
+
+ &__tab {
+ min-width: 5rem;
+ padding: 8px;
+ color: $dusty-gray;
+ font-family: Roboto;
+ font-size: 1rem;
+ text-align: center;
+ cursor: pointer;
+ border-bottom: none;
+ margin-right: 16px;
+
+ &:last-of-type {
+ margin-right: 0;
+ }
+
+ &--selected {
+ color: $curious-blue;
+ border-bottom: 3px solid $curious-blue;
+ }
+ }
+
+ &--full-width {
+ width: 100% !important;
+ }
+
+ &--full-height {
+ height: 100% !important;
+ max-height: initial !important;
+ min-height: initial !important;
+ }
+
+ &__content {
+ overflow-y: auto;
+ flex: 1;
+ }
+
+ &__warning-container {
+ background: $linen;
+ padding: 20px;
+ display: flex;
+ align-items: start;
+ }
+
+ &__warning-message {
+ padding-left: 15px;
+ }
+
+ &__warning-title {
+ font-weight: 500;
+ }
+
+ &__warning-icon {
+ padding-top: 5px;
+ }
+}
+
+@media screen and (max-width: 250px) {
+ .page-container {
+ &__footer {
+ flex-flow: column-reverse;
+ }
+
+ &__footer-button {
+ width: 100%;
+ margin-bottom: 1rem;
+ margin-right: 0;
+
+ &:first-of-type {
+ margin-bottom: 0;
+ }
+ }
+ }
+}
+
+@media screen and (max-width: 575px) {
+ .page-container {
+ height: 100%;
+ width: 100%;
+ overflow-y: auto;
+ background-color: $white;
+ border-radius: 0;
+ flex: 1;
+ }
+}
+
+@media screen and (min-width: 576px) {
+ .page-container {
+ max-height: 82vh;
+ min-height: 570px;
+ flex: 0 0 auto;
+ }
+}
diff --git a/ui/app/components/page-container/page-container-footer/page-container-footer.component.js b/ui/app/components/page-container/page-container-footer/page-container-footer.component.js
index 0458ae78a..3d15df294 100644
--- a/ui/app/components/page-container/page-container-footer/page-container-footer.component.js
+++ b/ui/app/components/page-container/page-container-footer/page-container-footer.component.js
@@ -10,6 +10,7 @@ export default class PageContainerFooter extends Component {
onSubmit: PropTypes.func,
submitText: PropTypes.string,
disabled: PropTypes.bool,
+ submitButtonType: PropTypes.string,
}
static contextTypes = {
@@ -23,6 +24,7 @@ export default class PageContainerFooter extends Component {
onSubmit,
submitText,
disabled,
+ submitButtonType,
} = this.props
return (
@@ -30,16 +32,16 @@ export default class PageContainerFooter extends Component {
<Button
type="default"
- large={true}
+ large
className="page-container__footer-button"
- onClick={() => onCancel()}
+ onClick={e => onCancel(e)}
>
{ cancelText || this.context.t('cancel') }
</Button>
<Button
- type="primary"
- large={true}
+ type={submitButtonType || 'primary'}
+ large
className="page-container__footer-button"
disabled={disabled}
onClick={e => onSubmit(e)}
diff --git a/ui/app/components/page-container/page-container-header.component.js b/ui/app/components/page-container/page-container-header.component.js
deleted file mode 100644
index 5c9d63221..000000000
--- a/ui/app/components/page-container/page-container-header.component.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-
-export default class PageContainerHeader extends Component {
-
- static propTypes = {
- title: PropTypes.string,
- subtitle: PropTypes.string,
- onClose: PropTypes.func,
- };
-
- render () {
- const { title, subtitle, onClose } = this.props
-
- return (
- <div className="page-container__header">
-
- <div className="page-container__title">
- {title}
- </div>
-
- <div className="page-container__subtitle">
- {subtitle}
- </div>
-
- <div
- className="page-container__header-close"
- onClick={() => onClose()}
- />
-
- </div>
- )
- }
-
-}
diff --git a/ui/app/components/page-container/page-container-header/page-container-header.component.js b/ui/app/components/page-container/page-container-header/page-container-header.component.js
index 28882edce..5a5de1e5a 100644
--- a/ui/app/components/page-container/page-container-header/page-container-header.component.js
+++ b/ui/app/components/page-container/page-container-header/page-container-header.component.js
@@ -4,13 +4,14 @@ import PropTypes from 'prop-types'
export default class PageContainerHeader extends Component {
static propTypes = {
- title: PropTypes.string.isRequired,
+ title: PropTypes.string,
subtitle: PropTypes.string,
onClose: PropTypes.func,
showBackButton: PropTypes.bool,
onBackButtonClick: PropTypes.func,
backButtonStyles: PropTypes.object,
backButtonString: PropTypes.string,
+ children: PropTypes.node,
};
renderHeaderRow () {
@@ -30,25 +31,33 @@ export default class PageContainerHeader extends Component {
}
render () {
- const { title, subtitle, onClose } = this.props
+ const { title, subtitle, onClose, children } = this.props
return (
<div className="page-container__header">
{ this.renderHeaderRow() }
- <div className="page-container__title">
- {title}
- </div>
+ { children }
- <div className="page-container__subtitle">
- {subtitle}
- </div>
+ {
+ title && <div className="page-container__title">
+ { title }
+ </div>
+ }
- <div
- className="page-container__header-close"
- onClick={() => onClose()}
- />
+ {
+ subtitle && <div className="page-container__subtitle">
+ { subtitle }
+ </div>
+ }
+
+ {
+ onClose && <div
+ className="page-container__header-close"
+ onClick={() => onClose()}
+ />
+ }
</div>
)
diff --git a/ui/app/components/pages/confirm-approve/confirm-approve.component.js b/ui/app/components/pages/confirm-approve/confirm-approve.component.js
new file mode 100644
index 000000000..d775b0362
--- /dev/null
+++ b/ui/app/components/pages/confirm-approve/confirm-approve.component.js
@@ -0,0 +1,30 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import ConfirmTransactionBase from '../confirm-transaction-base'
+
+export default class ConfirmApprove extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ tokenAddress: PropTypes.string,
+ toAddress: PropTypes.string,
+ tokenAmount: PropTypes.string,
+ tokenSymbol: PropTypes.string,
+ }
+
+ render () {
+ const { toAddress, tokenAddress, tokenAmount, tokenSymbol } = this.props
+
+ return (
+ <ConfirmTransactionBase
+ toAddress={toAddress}
+ identiconAddress={tokenAddress}
+ title={`${tokenAmount} ${tokenSymbol}`}
+ warning={`By approving this action, you grant permission for this contract to spend up to ${tokenAmount} of your ${tokenSymbol}.`}
+ hideSubtitle
+ />
+ )
+ }
+}
diff --git a/ui/app/components/pages/confirm-approve/confirm-approve.container.js b/ui/app/components/pages/confirm-approve/confirm-approve.container.js
new file mode 100644
index 000000000..040e499ae
--- /dev/null
+++ b/ui/app/components/pages/confirm-approve/confirm-approve.container.js
@@ -0,0 +1,28 @@
+import { connect } from 'react-redux'
+import ConfirmApprove from './confirm-approve.component'
+
+const mapStateToProps = state => {
+ const { confirmTransaction } = state
+ const {
+ tokenData = {},
+ txData: { txParams: { to: tokenAddress } = {} } = {},
+ tokenProps: { tokenSymbol } = {},
+ } = confirmTransaction
+ const { params = [] } = tokenData
+
+ let toAddress = ''
+ let tokenAmount = ''
+
+ if (params && params.length === 2) {
+ [{ value: toAddress }, { value: tokenAmount }] = params
+ }
+
+ return {
+ toAddress,
+ tokenAddress,
+ tokenAmount,
+ tokenSymbol,
+ }
+}
+
+export default connect(mapStateToProps)(ConfirmApprove)
diff --git a/ui/app/components/pages/confirm-approve/index.js b/ui/app/components/pages/confirm-approve/index.js
new file mode 100644
index 000000000..791297be7
--- /dev/null
+++ b/ui/app/components/pages/confirm-approve/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-approve.container'
diff --git a/ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.component.js b/ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.component.js
new file mode 100644
index 000000000..9bc0daab9
--- /dev/null
+++ b/ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.component.js
@@ -0,0 +1,64 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import ethUtil from 'ethereumjs-util'
+import ConfirmTransactionBase from '../confirm-transaction-base'
+
+export default class ConfirmDeployContract extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ txData: PropTypes.object,
+ }
+
+ renderData () {
+ const { t } = this.context
+ const {
+ txData: {
+ origin,
+ txParams: {
+ data,
+ } = {},
+ } = {},
+ } = this.props
+
+ return (
+ <div className="confirm-page-container-content__data">
+ <div className="confirm-page-container-content__data-box">
+ <div className="confirm-page-container-content__data-field">
+ <div className="confirm-page-container-content__data-field-label">
+ { `${t('origin')}:` }
+ </div>
+ <div>
+ { origin }
+ </div>
+ </div>
+ <div className="confirm-page-container-content__data-field">
+ <div className="confirm-page-container-content__data-field-label">
+ { `${t('bytes')}:` }
+ </div>
+ <div>
+ { ethUtil.toBuffer(data).length }
+ </div>
+ </div>
+ </div>
+ <div className="confirm-page-container-content__data-box-label">
+ { `${t('hexData')}:` }
+ </div>
+ <div className="confirm-page-container-content__data-box">
+ { data }
+ </div>
+ </div>
+ )
+ }
+
+ render () {
+ return (
+ <ConfirmTransactionBase
+ action={this.context.t('contractDeployment')}
+ dataComponent={this.renderData()}
+ />
+ )
+ }
+}
diff --git a/ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.container.js b/ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.container.js
new file mode 100644
index 000000000..336ee83ea
--- /dev/null
+++ b/ui/app/components/pages/confirm-deploy-contract/confirm-deploy-contract.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import ConfirmDeployContract from './confirm-deploy-contract.component'
+
+const mapStateToProps = state => {
+ const { confirmTransaction: { txData } = {} } = state
+
+ return {
+ txData,
+ }
+}
+
+export default connect(mapStateToProps)(ConfirmDeployContract)
diff --git a/ui/app/components/pages/confirm-deploy-contract/index.js b/ui/app/components/pages/confirm-deploy-contract/index.js
new file mode 100644
index 000000000..c4fb01b52
--- /dev/null
+++ b/ui/app/components/pages/confirm-deploy-contract/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-deploy-contract.container'
diff --git a/ui/app/components/pages/confirm-send-ether/confirm-send-ether.component.js b/ui/app/components/pages/confirm-send-ether/confirm-send-ether.component.js
new file mode 100644
index 000000000..c590bacf3
--- /dev/null
+++ b/ui/app/components/pages/confirm-send-ether/confirm-send-ether.component.js
@@ -0,0 +1,31 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import ConfirmTransactionBase from '../confirm-transaction-base'
+import { SEND_ROUTE } from '../../../routes'
+
+export default class ConfirmSendEther extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ editTransaction: PropTypes.func,
+ history: PropTypes.object,
+ }
+
+ handleEdit ({ txData }) {
+ const { editTransaction, history } = this.props
+ editTransaction(txData)
+ history.push(SEND_ROUTE)
+ }
+
+ render () {
+ return (
+ <ConfirmTransactionBase
+ action={this.context.t('confirm')}
+ hideData
+ onEdit={confirmTransactionData => this.handleEdit(confirmTransactionData)}
+ />
+ )
+ }
+}
diff --git a/ui/app/components/pages/confirm-send-ether/confirm-send-ether.container.js b/ui/app/components/pages/confirm-send-ether/confirm-send-ether.container.js
new file mode 100644
index 000000000..898735d1a
--- /dev/null
+++ b/ui/app/components/pages/confirm-send-ether/confirm-send-ether.container.js
@@ -0,0 +1,37 @@
+import { connect } from 'react-redux'
+import { compose } from 'recompose'
+import { withRouter } from 'react-router-dom'
+import { updateSend } from '../../../actions'
+import { clearConfirmTransaction } from '../../../ducks/confirm-transaction.duck'
+import ConfirmSendEther from './confirm-send-ether.component'
+
+const mapDispatchToProps = dispatch => {
+ return {
+ editTransaction: txData => {
+ const { id, txParams } = txData
+ const {
+ gas: gasLimit,
+ gasPrice,
+ to,
+ value: amount,
+ } = txParams
+
+ dispatch(updateSend({
+ gasLimit,
+ gasPrice,
+ gasTotal: null,
+ to,
+ amount,
+ errors: { to: null, amount: null },
+ editingTransactionId: id && id.toString(),
+ }))
+
+ dispatch(clearConfirmTransaction())
+ },
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(null, mapDispatchToProps)
+)(ConfirmSendEther)
diff --git a/ui/app/components/pages/confirm-send-ether/index.js b/ui/app/components/pages/confirm-send-ether/index.js
new file mode 100644
index 000000000..2d5767c39
--- /dev/null
+++ b/ui/app/components/pages/confirm-send-ether/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-send-ether.container'
diff --git a/ui/app/components/pages/confirm-send-token/confirm-send-token.component.js b/ui/app/components/pages/confirm-send-token/confirm-send-token.component.js
new file mode 100644
index 000000000..46ad9ccab
--- /dev/null
+++ b/ui/app/components/pages/confirm-send-token/confirm-send-token.component.js
@@ -0,0 +1,39 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import ConfirmTransactionBase from '../confirm-transaction-base'
+import { SEND_ROUTE } from '../../../routes'
+
+export default class ConfirmSendToken extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ history: PropTypes.object,
+ tokenAddress: PropTypes.string,
+ toAddress: PropTypes.string,
+ numberOfTokens: PropTypes.number,
+ tokenSymbol: PropTypes.string,
+ editTransaction: PropTypes.func,
+ }
+
+ handleEdit (confirmTransactionData) {
+ const { editTransaction, history } = this.props
+ editTransaction(confirmTransactionData)
+ history.push(SEND_ROUTE)
+ }
+
+ render () {
+ const { toAddress, tokenAddress, tokenSymbol, numberOfTokens } = this.props
+
+ return (
+ <ConfirmTransactionBase
+ toAddress={toAddress}
+ identiconAddress={tokenAddress}
+ title={`${numberOfTokens} ${tokenSymbol}`}
+ onEdit={confirmTransactionData => this.handleEdit(confirmTransactionData)}
+ hideSubtitle
+ />
+ )
+ }
+}
diff --git a/ui/app/components/pages/confirm-send-token/confirm-send-token.container.js b/ui/app/components/pages/confirm-send-token/confirm-send-token.container.js
new file mode 100644
index 000000000..2d7efeed6
--- /dev/null
+++ b/ui/app/components/pages/confirm-send-token/confirm-send-token.container.js
@@ -0,0 +1,72 @@
+import { connect } from 'react-redux'
+import { compose } from 'recompose'
+import { withRouter } from 'react-router-dom'
+import ConfirmSendToken from './confirm-send-token.component'
+import { calcTokenAmount } from '../../../token-util'
+import { clearConfirmTransaction } from '../../../ducks/confirm-transaction.duck'
+import { setSelectedToken, updateSend, showSendTokenPage } from '../../../actions'
+import { conversionUtil } from '../../../conversion-util'
+
+const mapStateToProps = state => {
+ const { confirmTransaction } = state
+ const {
+ tokenData = {},
+ tokenProps: { tokenSymbol, tokenDecimals } = {},
+ txData: { txParams: { to: tokenAddress } = {} } = {},
+ } = confirmTransaction
+ const { params = [] } = tokenData
+
+ let toAddress = ''
+ let tokenAmount = ''
+
+ if (params && params.length === 2) {
+ [{ value: toAddress }, { value: tokenAmount }] = params
+ }
+
+ const numberOfTokens = tokenAmount && tokenDecimals
+ ? calcTokenAmount(tokenAmount, tokenDecimals)
+ : 0
+
+ return {
+ toAddress,
+ tokenAddress,
+ tokenSymbol,
+ numberOfTokens,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ editTransaction: ({ txData, tokenData, tokenProps }) => {
+ const { txParams: { to: tokenAddress, gas: gasLimit, gasPrice } = {}, id } = txData
+ const { params = [] } = tokenData
+ const { value: to } = params[0] || {}
+ const { value: tokenAmountInDec } = params[1] || {}
+ const tokenAmountInHex = conversionUtil(tokenAmountInDec, {
+ fromNumericBase: 'dec',
+ toNumericBase: 'hex',
+ })
+ dispatch(setSelectedToken(tokenAddress))
+ dispatch(updateSend({
+ gasLimit,
+ gasPrice,
+ gasTotal: null,
+ to,
+ amount: tokenAmountInHex,
+ errors: { to: null, amount: null },
+ editingTransactionId: id && id.toString(),
+ token: {
+ ...tokenProps,
+ address: tokenAddress,
+ },
+ }))
+ dispatch(clearConfirmTransaction())
+ dispatch(showSendTokenPage())
+ },
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(ConfirmSendToken)
diff --git a/ui/app/components/pages/confirm-send-token/index.js b/ui/app/components/pages/confirm-send-token/index.js
new file mode 100644
index 000000000..409b6ef3d
--- /dev/null
+++ b/ui/app/components/pages/confirm-send-token/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-send-token.container'
diff --git a/ui/app/components/pages/confirm-send-token/index.scss b/ui/app/components/pages/confirm-send-token/index.scss
new file mode 100644
index 000000000..0476749f6
--- /dev/null
+++ b/ui/app/components/pages/confirm-send-token/index.scss
@@ -0,0 +1,19 @@
+.confirm-send-token {
+ &__title {
+ padding: 4px 0;
+ display: flex;
+ align-items: center;
+ }
+
+ &__identicon {
+ flex: 0 0 auto;
+ }
+
+ &__title-text {
+ font-size: 2.25rem;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding-left: 8px;
+ }
+}
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
new file mode 100644
index 000000000..3b60a5d5d
--- /dev/null
+++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
@@ -0,0 +1,382 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container'
+import { formatCurrency, getHexGasTotal } from '../../../helpers/confirm-transaction/util'
+import { isBalanceSufficient } from '../../send_/send.utils'
+import { DEFAULT_ROUTE } from '../../../routes'
+import { conversionGreaterThan } from '../../../conversion-util'
+import { MIN_GAS_LIMIT_DEC } from '../../send_/send.constants'
+
+export default class ConfirmTransactionBase extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ match: PropTypes.object,
+ history: PropTypes.object,
+ // Redux props
+ txData: PropTypes.object,
+ tokenData: PropTypes.object,
+ tokenProps: PropTypes.object,
+ isTxReprice: PropTypes.bool,
+ nonce: PropTypes.string,
+ fromName: PropTypes.string,
+ fromAddress: PropTypes.string,
+ toName: PropTypes.string,
+ toAddress: PropTypes.string,
+ transactionStatus: PropTypes.string,
+ ethTransactionAmount: PropTypes.string,
+ ethTransactionFee: PropTypes.string,
+ ethTransactionTotal: PropTypes.string,
+ fiatTransactionAmount: PropTypes.string,
+ fiatTransactionFee: PropTypes.string,
+ fiatTransactionTotal: PropTypes.string,
+ hexGasTotal: PropTypes.string,
+ balance: PropTypes.string,
+ currentCurrency: PropTypes.string,
+ conversionRate: PropTypes.number,
+ setTransactionToConfirm: PropTypes.func,
+ clearConfirmTransaction: PropTypes.func,
+ cancelTransaction: PropTypes.func,
+ clearSend: PropTypes.func,
+ sendTransaction: PropTypes.func,
+ editTransaction: PropTypes.func,
+ showCustomizeGasModal: PropTypes.func,
+ updateGasAndCalculate: PropTypes.func,
+ showTransactionConfirmedModal: PropTypes.func,
+ // Component props
+ action: PropTypes.string,
+ hideDetails: PropTypes.bool,
+ hideData: PropTypes.bool,
+ detailsComponent: PropTypes.node,
+ dataComponent: PropTypes.node,
+ summaryComponent: PropTypes.node,
+ contentComponent: PropTypes.node,
+ title: PropTypes.string,
+ subtitle: PropTypes.string,
+ hideSubtitle: PropTypes.bool,
+ valid: PropTypes.bool,
+ errorMessage: PropTypes.string,
+ warning: PropTypes.string,
+ identiconAddress: PropTypes.string,
+ onEdit: PropTypes.func,
+ onEditGas: PropTypes.func,
+ onCancel: PropTypes.func,
+ onSubmit: PropTypes.func,
+ }
+
+ componentDidMount () {
+ const { match: { params: { id } = {} }, setTransactionToConfirm } = this.props
+ setTransactionToConfirm(id)
+ }
+
+ componentDidUpdate (prevProps) {
+ const {
+ transactionStatus,
+ showTransactionConfirmedModal,
+ history,
+ clearConfirmTransaction,
+ match: { params: { id } = {} },
+ setTransactionToConfirm,
+ txData,
+ } = this.props
+
+ if (transactionStatus === 'dropped') {
+ showTransactionConfirmedModal({
+ onHide: () => {
+ clearConfirmTransaction()
+ history.push(DEFAULT_ROUTE)
+ },
+ })
+
+ return
+ }
+
+ if (id && id !== txData.id + '') {
+ setTransactionToConfirm(id)
+ }
+ }
+
+ getError () {
+ const INSUFFICIENT_FUNDS_ERROR = this.context.t('insufficientFunds')
+ const TRANSACTION_ERROR = this.context.t('transactionError')
+ const {
+ balance,
+ conversionRate,
+ hexGasTotal,
+ txData: {
+ simulationFails,
+ txParams: {
+ value: amount,
+ } = {},
+ } = {},
+ } = this.props
+
+ const insufficientBalance = balance && !isBalanceSufficient({
+ amount,
+ gasTotal: hexGasTotal || '0x0',
+ balance,
+ conversionRate,
+ })
+
+ if (insufficientBalance) {
+ return {
+ valid: false,
+ errorMessage: INSUFFICIENT_FUNDS_ERROR,
+ }
+ }
+
+ if (simulationFails) {
+ return {
+ valid: false,
+ errorMessage: TRANSACTION_ERROR,
+ }
+ }
+
+ return {
+ valid: true,
+ }
+ }
+
+ validateEditGas ({ gasLimit, gasPrice }) {
+ const { t } = this.context
+ const {
+ balance,
+ conversionRate,
+ txData: {
+ txParams: {
+ value: amount,
+ } = {},
+ } = {},
+ } = this.props
+
+ const INSUFFICIENT_FUNDS_ERROR = t('insufficientFunds')
+ const GAS_LIMIT_TOO_LOW_ERROR = t('gasLimitTooLow')
+
+ const gasTotal = getHexGasTotal({ gasLimit, gasPrice })
+ const hasSufficientBalance = isBalanceSufficient({
+ amount,
+ gasTotal,
+ balance,
+ conversionRate,
+ })
+
+ if (!hasSufficientBalance) {
+ return {
+ valid: false,
+ errorMessage: INSUFFICIENT_FUNDS_ERROR,
+ }
+ }
+
+ const gasLimitTooLow = gasLimit && conversionGreaterThan(
+ {
+ value: MIN_GAS_LIMIT_DEC,
+ fromNumericBase: 'dec',
+ conversionRate,
+ },
+ {
+ value: gasLimit,
+ fromNumericBase: 'hex',
+ },
+ )
+
+ if (gasLimitTooLow) {
+ return {
+ valid: false,
+ errorMessage: GAS_LIMIT_TOO_LOW_ERROR,
+ }
+ }
+
+ return {
+ valid: true,
+ }
+ }
+
+ handleEditGas () {
+ const { onEditGas, showCustomizeGasModal, txData, updateGasAndCalculate } = this.props
+
+ if (onEditGas) {
+ onEditGas()
+ } else {
+ showCustomizeGasModal({
+ txData,
+ onSubmit: txData => updateGasAndCalculate(txData),
+ validate: ({ gasLimit, gasPrice }) => this.validateEditGas({ gasLimit, gasPrice }),
+ })
+ }
+ }
+
+ renderDetails () {
+ const {
+ detailsComponent,
+ fiatTransactionFee,
+ ethTransactionFee,
+ currentCurrency,
+ fiatTransactionTotal,
+ ethTransactionTotal,
+ hideDetails,
+ } = this.props
+
+ if (hideDetails) {
+ return null
+ }
+
+ return (
+ detailsComponent || (
+ <div className="confirm-page-container-content__details">
+ <div className="confirm-page-container-content__gas-fee">
+ <ConfirmDetailRow
+ label="Gas Fee"
+ fiatFee={formatCurrency(fiatTransactionFee, currentCurrency)}
+ ethFee={ethTransactionFee}
+ headerText="Edit"
+ headerTextClassName="confirm-detail-row__header-text--edit"
+ onHeaderClick={() => this.handleEditGas()}
+ />
+ </div>
+ <div>
+ <ConfirmDetailRow
+ label="Total"
+ fiatFee={formatCurrency(fiatTransactionTotal, currentCurrency)}
+ ethFee={ethTransactionTotal}
+ headerText="Amount + Gas Fee"
+ headerTextClassName="confirm-detail-row__header-text--total"
+ fiatFeeColor="#2f9ae0"
+ />
+ </div>
+ </div>
+ )
+ )
+ }
+
+ renderData () {
+ const { t } = this.context
+ const {
+ txData: {
+ txParams: {
+ data,
+ } = {},
+ } = {},
+ tokenData: {
+ name,
+ params,
+ } = {},
+ hideData,
+ dataComponent,
+ } = this.props
+
+ if (hideData) {
+ return null
+ }
+
+ return dataComponent || (
+ <div className="confirm-page-container-content__data">
+ <div className="confirm-page-container-content__data-box-label">
+ {`${t('functionType')}:`}
+ <span className="confirm-page-container-content__function-type">
+ { name }
+ </span>
+ </div>
+ <div className="confirm-page-container-content__data-box">
+ { JSON.stringify(params, null, 2) }
+ </div>
+ <div className="confirm-page-container-content__data-box-label">
+ {`${t('hexData')}:`}
+ </div>
+ <div className="confirm-page-container-content__data-box">
+ { data }
+ </div>
+ </div>
+ )
+ }
+
+ handleEdit () {
+ const { txData, tokenData, tokenProps, onEdit } = this.props
+ onEdit({ txData, tokenData, tokenProps })
+ }
+
+ handleCancel () {
+ const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction } = this.props
+
+ if (onCancel) {
+ onCancel(txData)
+ } else {
+ cancelTransaction(txData)
+ .then(() => {
+ clearConfirmTransaction()
+ history.push(DEFAULT_ROUTE)
+ })
+ }
+ }
+
+ handleSubmit () {
+ const { sendTransaction, clearConfirmTransaction, txData, history, onSubmit } = this.props
+
+ if (onSubmit) {
+ onSubmit(txData)
+ } else {
+ sendTransaction(txData)
+ .then(() => {
+ clearConfirmTransaction()
+ history.push(DEFAULT_ROUTE)
+ })
+ }
+ }
+
+ render () {
+ const {
+ isTxReprice,
+ fromName,
+ fromAddress,
+ toName,
+ toAddress,
+ tokenData,
+ ethTransactionAmount,
+ fiatTransactionAmount,
+ valid: propsValid,
+ errorMessage: propsErrorMessage,
+ currentCurrency,
+ action,
+ title,
+ subtitle,
+ hideSubtitle,
+ identiconAddress,
+ summaryComponent,
+ contentComponent,
+ onEdit,
+ nonce,
+ warning,
+ } = this.props
+
+ const { name } = tokenData
+ const fiatConvertedAmount = formatCurrency(fiatTransactionAmount, currentCurrency)
+ const { valid, errorMessage } = this.getError()
+
+ return (
+ <ConfirmPageContainer
+ fromName={fromName}
+ fromAddress={fromAddress}
+ toName={toName}
+ toAddress={toAddress}
+ showEdit={onEdit && !isTxReprice}
+ action={action || name}
+ title={title || `${fiatConvertedAmount} ${currentCurrency.toUpperCase()}`}
+ subtitle={subtitle || `\u2666 ${ethTransactionAmount}`}
+ hideSubtitle={hideSubtitle}
+ summaryComponent={summaryComponent}
+ detailsComponent={this.renderDetails()}
+ dataComponent={this.renderData()}
+ contentComponent={contentComponent}
+ nonce={nonce}
+ identiconAddress={identiconAddress}
+ errorMessage={propsErrorMessage || errorMessage}
+ warning={warning}
+ valid={propsValid || valid}
+ onEdit={() => this.handleEdit()}
+ onCancel={() => this.handleCancel()}
+ onSubmit={() => this.handleSubmit()}
+ />
+ )
+ }
+}
diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
new file mode 100644
index 000000000..ea1a1d296
--- /dev/null
+++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
@@ -0,0 +1,95 @@
+import { connect } from 'react-redux'
+import { compose } from 'recompose'
+import { withRouter } from 'react-router-dom'
+import R from 'ramda'
+import ConfirmTransactionBase from './confirm-transaction-base.component'
+import {
+ setTransactionToConfirm,
+ clearConfirmTransaction,
+ updateGasAndCalculate,
+} from '../../../ducks/confirm-transaction.duck'
+import { clearSend, cancelTx, updateAndApproveTx, showModal } from '../../../actions'
+
+const mapStateToProps = (state, props) => {
+ const { toAddress: propsToAddress } = props
+ const { confirmTransaction, metamask } = state
+ const {
+ ethTransactionAmount,
+ ethTransactionFee,
+ ethTransactionTotal,
+ fiatTransactionAmount,
+ fiatTransactionFee,
+ fiatTransactionTotal,
+ hexGasTotal,
+ tokenData,
+ txData,
+ tokenProps,
+ nonce,
+ } = confirmTransaction
+ const { txParams = {}, lastGasPrice, id: transactionId } = txData
+ const { from: fromAddress, to: txParamsToAddress } = txParams
+ const {
+ conversionRate,
+ identities,
+ currentCurrency,
+ accounts,
+ selectedAddress,
+ selectedAddressTxList,
+ } = metamask
+
+ const { balance } = accounts[selectedAddress]
+ const { name: fromName } = identities[selectedAddress]
+ const toAddress = propsToAddress || txParamsToAddress
+ const toName = identities[toAddress] && identities[toAddress].name
+ const isTxReprice = Boolean(lastGasPrice)
+
+ const transaction = R.find(({ id }) => id === transactionId)(selectedAddressTxList)
+ const transactionStatus = transaction ? transaction.status : ''
+
+ return {
+ balance,
+ fromAddress,
+ fromName,
+ toAddress,
+ toName,
+ ethTransactionAmount,
+ ethTransactionFee,
+ ethTransactionTotal,
+ fiatTransactionAmount,
+ fiatTransactionFee,
+ fiatTransactionTotal,
+ hexGasTotal,
+ txData,
+ tokenData,
+ tokenProps,
+ isTxReprice,
+ currentCurrency,
+ conversionRate,
+ transactionStatus,
+ nonce,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ setTransactionToConfirm: transactionId => dispatch(setTransactionToConfirm(transactionId)),
+ clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
+ clearSend: () => dispatch(clearSend()),
+ showTransactionConfirmedModal: ({ onHide }) => {
+ return dispatch(showModal({ name: 'TRANSACTION_CONFIRMED', onHide }))
+ },
+ showCustomizeGasModal: ({ txData, onSubmit, validate }) => {
+ return dispatch(showModal({ name: 'CONFIRM_CUSTOMIZE_GAS', txData, onSubmit, validate }))
+ },
+ updateGasAndCalculate: ({ gasLimit, gasPrice }) => {
+ return dispatch(updateGasAndCalculate({ gasLimit, gasPrice }))
+ },
+ cancelTransaction: ({ id }) => dispatch(cancelTx({ id })),
+ sendTransaction: txData => dispatch(updateAndApproveTx(txData)),
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(ConfirmTransactionBase)
diff --git a/ui/app/components/pages/confirm-transaction-base/index.js b/ui/app/components/pages/confirm-transaction-base/index.js
new file mode 100644
index 000000000..9996e9aeb
--- /dev/null
+++ b/ui/app/components/pages/confirm-transaction-base/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-transaction-base.container'
diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
new file mode 100644
index 000000000..5f337ab61
--- /dev/null
+++ b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
@@ -0,0 +1,71 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import { Redirect } from 'react-router-dom'
+import R from 'ramda'
+import Loading from '../../loading-screen'
+import {
+ CONFIRM_DEPLOY_CONTRACT_ROUTE,
+ CONFIRM_SEND_ETHER_ROUTE,
+ CONFIRM_SEND_TOKEN_ROUTE,
+ CONFIRM_APPROVE_ROUTE,
+ CONFIRM_TOKEN_METHOD_ROUTE,
+ SIGNATURE_REQUEST_ROUTE,
+} from '../../../routes'
+import { isConfirmDeployContract, getTokenData } from './confirm-transaction-switch.util'
+import { TOKEN_METHOD_TRANSFER, TOKEN_METHOD_APPROVE } from './confirm-transaction-switch.constants'
+
+export default class ConfirmTransactionSwitch extends Component {
+ static propTypes = {
+ unconfirmedTransactions: PropTypes.array,
+ match: PropTypes.object,
+ }
+
+ getTransaction () {
+ const { unconfirmedTransactions, match } = this.props
+ const { params: { id: paramsTransactionId } = {} } = match
+
+ return paramsTransactionId
+ ? R.find(({ id }) => id + '' === paramsTransactionId)(unconfirmedTransactions)
+ : unconfirmedTransactions[0]
+ }
+
+ redirectToTransaction (txData) {
+ const { id, txParams: { data } } = txData
+
+ if (isConfirmDeployContract(txData)) {
+ return <Redirect to={{ pathname: `${CONFIRM_DEPLOY_CONTRACT_ROUTE}/${id}` }} />
+ }
+
+ if (data) {
+ const tokenData = getTokenData(data)
+ const { name: tokenMethodName } = tokenData || {}
+
+ switch (tokenMethodName) {
+ case TOKEN_METHOD_TRANSFER:
+ return <Redirect to={{ pathname: `${CONFIRM_SEND_TOKEN_ROUTE}/${id}` }} />
+ case TOKEN_METHOD_APPROVE:
+ return <Redirect to={{ pathname: `${CONFIRM_APPROVE_ROUTE}/${id}` }} />
+ default:
+ return <Redirect to={{ pathname: `${CONFIRM_TOKEN_METHOD_ROUTE}/${id}` }} />
+ }
+ }
+
+ return <Redirect to={{ pathname: `${CONFIRM_SEND_ETHER_ROUTE}/${id}` }} />
+ }
+
+ render () {
+ const txData = this.getTransaction()
+
+ if (!txData) {
+ return <Loading />
+ }
+
+ if (txData.txParams) {
+ return this.redirectToTransaction(txData)
+ } else if (txData.msgParams) {
+ return <Redirect to={{ pathname: SIGNATURE_REQUEST_ROUTE }} />
+ }
+
+ return <Loading />
+ }
+}
diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js
new file mode 100644
index 000000000..622d2a37a
--- /dev/null
+++ b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js
@@ -0,0 +1,2 @@
+export const TOKEN_METHOD_TRANSFER = 'transfer'
+export const TOKEN_METHOD_APPROVE = 'approve'
diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.container.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.container.js
new file mode 100644
index 000000000..cb6008acb
--- /dev/null
+++ b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.container.js
@@ -0,0 +1,11 @@
+import { connect } from 'react-redux'
+import ConfirmTransactionSwitch from './confirm-transaction-switch.component'
+import { unconfirmedTransactionsListSelector } from '../../../selectors/confirm-transaction'
+
+const mapStateToProps = state => {
+ return {
+ unconfirmedTransactions: unconfirmedTransactionsListSelector(state),
+ }
+}
+
+export default connect(mapStateToProps)(ConfirmTransactionSwitch)
diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.util.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.util.js
new file mode 100644
index 000000000..dd1dec6ae
--- /dev/null
+++ b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.util.js
@@ -0,0 +1,12 @@
+import abi from 'human-standard-token-abi'
+import abiDecoder from 'abi-decoder'
+abiDecoder.addABI(abi)
+
+export function isConfirmDeployContract (txData = {}) {
+ const { txParams = {} } = txData
+ return !txParams.to
+}
+
+export function getTokenData (data = {}) {
+ return abiDecoder.decodeMethod(data)
+}
diff --git a/ui/app/components/pages/confirm-transaction-switch/index.js b/ui/app/components/pages/confirm-transaction-switch/index.js
new file mode 100644
index 000000000..c288acb1a
--- /dev/null
+++ b/ui/app/components/pages/confirm-transaction-switch/index.js
@@ -0,0 +1,2 @@
+import ConfirmTransactionSwitch from './confirm-transaction-switch.container'
+module.exports = ConfirmTransactionSwitch
diff --git a/ui/app/components/pages/confirm-transaction/confirm-transaction.component.js b/ui/app/components/pages/confirm-transaction/confirm-transaction.component.js
new file mode 100644
index 000000000..cc0b019e0
--- /dev/null
+++ b/ui/app/components/pages/confirm-transaction/confirm-transaction.component.js
@@ -0,0 +1,59 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import { Switch, Route } from 'react-router-dom'
+import ConfirmTransactionSwitch from '../confirm-transaction-switch'
+import ConfirmTransactionBase from '../confirm-transaction-base'
+import ConfirmSendEther from '../confirm-send-ether'
+import ConfirmSendToken from '../confirm-send-token'
+import ConfirmDeployContract from '../confirm-deploy-contract'
+import ConfirmApprove from '../confirm-approve'
+import ConfTx from '../../../conf-tx'
+import {
+ DEFAULT_ROUTE,
+ CONFIRM_TRANSACTION_ROUTE,
+ CONFIRM_DEPLOY_CONTRACT_ROUTE,
+ CONFIRM_SEND_ETHER_ROUTE,
+ CONFIRM_SEND_TOKEN_ROUTE,
+ CONFIRM_APPROVE_ROUTE,
+ CONFIRM_TOKEN_METHOD_ROUTE,
+ SIGNATURE_REQUEST_ROUTE,
+} from '../../../routes'
+
+export default class ConfirmTransaction extends Component {
+ static propTypes = {
+ history: PropTypes.object.isRequired,
+ totalUnapprovedCount: PropTypes.number.isRequired,
+ match: PropTypes.object,
+ send: PropTypes.object,
+ }
+
+ componentDidMount () {
+ const { totalUnapprovedCount = 0, send = {}, history } = this.props
+
+ if (!totalUnapprovedCount && !send.to) {
+ history.replace(DEFAULT_ROUTE)
+ }
+ }
+
+ render () {
+ return (
+ <Switch>
+ <Route
+ exact
+ path={`${CONFIRM_DEPLOY_CONTRACT_ROUTE}/:id?`}
+ component={ConfirmDeployContract}
+ />
+ <Route
+ exact
+ path={`${CONFIRM_TOKEN_METHOD_ROUTE}/:id?`}
+ component={ConfirmTransactionBase}
+ />
+ <Route exact path={`${CONFIRM_SEND_ETHER_ROUTE}/:id?`} component={ConfirmSendEther} />
+ <Route exact path={`${CONFIRM_SEND_TOKEN_ROUTE}/:id?`} component={ConfirmSendToken} />
+ <Route exact path={`${CONFIRM_APPROVE_ROUTE}/:id?`} component={ConfirmApprove} />
+ <Route exact path={SIGNATURE_REQUEST_ROUTE} component={ConfTx} />
+ <Route path={`${CONFIRM_TRANSACTION_ROUTE}/:id?`} component={ConfirmTransactionSwitch} />
+ </Switch>
+ )
+ }
+}
diff --git a/ui/app/components/pages/confirm-transaction/confirm-transaction.container.js b/ui/app/components/pages/confirm-transaction/confirm-transaction.container.js
new file mode 100644
index 000000000..6779e69b7
--- /dev/null
+++ b/ui/app/components/pages/confirm-transaction/confirm-transaction.container.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux'
+import { compose } from 'recompose'
+import { withRouter } from 'react-router-dom'
+import ConfirmTransaction from './confirm-transaction.component'
+import { getTotalUnapprovedCount } from '../../../selectors'
+
+const mapStateToProps = (state, props) => {
+ const { metamask: { send } } = state
+
+ return {
+ totalUnapprovedCount: getTotalUnapprovedCount(state),
+ send,
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps),
+)(ConfirmTransaction)
diff --git a/ui/app/components/pages/confirm-transaction/index.js b/ui/app/components/pages/confirm-transaction/index.js
new file mode 100644
index 000000000..4bf42d85c
--- /dev/null
+++ b/ui/app/components/pages/confirm-transaction/index.js
@@ -0,0 +1,2 @@
+import ConfirmTransaction from './confirm-transaction.container'
+module.exports = ConfirmTransaction
diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js
index c53413d3b..86bd32c8a 100644
--- a/ui/app/components/pages/home.js
+++ b/ui/app/components/pages/home.js
@@ -83,51 +83,6 @@ class Home extends Component {
})
}
- // if (!props.noActiveNotices) {
- // log.debug('rendering notice screen for unread notices.')
- // return h(NoticeScreen, {
- // notice: props.nextUnreadNotice,
- // key: 'NoticeScreen',
- // onConfirm: () => props.dispatch(actions.markNoticeRead(props.nextUnreadNotice)),
- // })
- // } else if (props.lostAccounts && props.lostAccounts.length > 0) {
- // log.debug('rendering notice screen for lost accounts view.')
- // return h(NoticeScreen, {
- // notice: generateLostAccountsNotice(props.lostAccounts),
- // key: 'LostAccountsNotice',
- // onConfirm: () => props.dispatch(actions.markAccountsFound()),
- // })
- // }
-
- // if (props.seedWords) {
- // log.debug('rendering seed words')
- // return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
- // }
-
- // show initialize screen
- // if (!isInitialized || forgottenPassword) {
- // // show current view
- // log.debug('rendering an initialize screen')
- // // switch (props.currentView.name) {
-
- // // case 'restoreVault':
- // // log.debug('rendering restore vault screen')
- // // return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
-
- // // default:
- // // log.debug('rendering menu screen')
- // // return h(InitializeScreen, {key: 'menuScreenInit'})
- // // }
- // }
-
- // // show unlock screen
- // if (!props.isUnlocked) {
- // return h(MainContainer, {
- // currentViewName: props.currentView.name,
- // isUnlocked: props.isUnlocked,
- // })
- // }
-
// show current view
switch (currentView.name) {
@@ -135,59 +90,10 @@ class Home extends Component {
log.debug('rendering main container')
return h(MainContainer, {key: 'account-detail'})
- // case 'sendTransaction':
- // log.debug('rendering send tx screen')
-
- // // Going to leave this here until we are ready to delete SendTransactionScreen v1
- // // const SendComponentToRender = checkFeatureToggle('send-v2')
- // // ? SendTransactionScreen2
- // // : SendTransactionScreen
-
- // return h(SendTransactionScreen2, {key: 'send-transaction'})
-
- // case 'sendToken':
- // log.debug('rendering send token screen')
-
- // // Going to leave this here until we are ready to delete SendTransactionScreen v1
- // // const SendTokenComponentToRender = checkFeatureToggle('send-v2')
- // // ? SendTransactionScreen2
- // // : SendTokenScreen
-
- // return h(SendTransactionScreen2, {key: 'sendToken'})
-
case 'newKeychain':
log.debug('rendering new keychain screen')
return h(NewKeyChainScreen, {key: 'new-keychain'})
- // case 'confTx':
- // log.debug('rendering confirm tx screen')
- // return h(Redirect, {
- // to: {
- // pathname: CONFIRM_TRANSACTION_ROUTE,
- // },
- // })
- // return h(ConfirmTxScreen, {key: 'confirm-tx'})
-
- // case 'add-token':
- // log.debug('rendering add-token screen from unlock screen.')
- // return h(AddTokenScreen, {key: 'add-token'})
-
- // case 'config':
- // log.debug('rendering config screen')
- // return h(Settings, {key: 'config'})
-
- // case 'import-menu':
- // log.debug('rendering import screen')
- // return h(Import, {key: 'import-menu'})
-
- // case 'reveal-seed-conf':
- // log.debug('rendering reveal seed confirmation screen')
- // return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
-
- // case 'info':
- // log.debug('rendering info screen')
- // return h(Settings, {key: 'info', tab: 'info'})
-
case 'buyEth':
log.debug('rendering buy ether screen')
return h(BuyView, {key: 'buyEthView'})
diff --git a/ui/app/components/pages/index.scss b/ui/app/components/pages/index.scss
index b15c59863..8b333b6a8 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 './confirm-send-token/index';
diff --git a/ui/app/components/send_/send-footer/send-footer.component.js b/ui/app/components/send_/send-footer/send-footer.component.js
index 6471ae1a3..ff42e58de 100644
--- a/ui/app/components/send_/send-footer/send-footer.component.js
+++ b/ui/app/components/send_/send-footer/send-footer.component.js
@@ -48,6 +48,7 @@ export default class SendFooter extends Component {
// updateTx,
update,
toAccounts,
+ history,
} = this.props
// Should not be needed because submit should be disabled if there are errors.
@@ -60,7 +61,7 @@ export default class SendFooter extends Component {
// TODO: add nickname functionality
addToAddressBookIfNew(to, toAccounts)
- editingTransactionId
+ const promise = editingTransactionId
? update({
amount,
editingTransactionId,
@@ -73,7 +74,8 @@ export default class SendFooter extends Component {
})
: sign({ selectedToken, to, amount, from, gas, gasPrice })
- this.props.history.push(CONFIRM_TRANSACTION_ROUTE)
+ Promise.resolve(promise)
+ .then(() => history.push(CONFIRM_TRANSACTION_ROUTE))
}
formShouldBeDisabled () {
diff --git a/ui/app/components/send_/send-footer/send-footer.container.js b/ui/app/components/send_/send-footer/send-footer.container.js
index 260ff40bc..0af6fcfa1 100644
--- a/ui/app/components/send_/send-footer/send-footer.container.js
+++ b/ui/app/components/send_/send-footer/send-footer.container.js
@@ -87,7 +87,7 @@ function mapDispatchToProps (dispatch) {
unapprovedTxs,
})
- dispatch(updateTransaction(editingTx))
+ return dispatch(updateTransaction(editingTx))
},
addToAddressBookIfNew: (newAddress, toAccounts, nickname = '') => {
const hexPrefixedAddress = ethUtil.addHexPrefix(newAddress)
diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js
index c4537f335..aa255c3d4 100644
--- a/ui/app/components/send_/send.utils.js
+++ b/ui/app/components/send_/send.utils.js
@@ -37,7 +37,7 @@ module.exports = {
removeLeadingZeroes,
}
-function calcGasTotal (gasLimit, gasPrice) {
+function calcGasTotal (gasLimit = '0', gasPrice = '0') {
return multiplyCurrencies(gasLimit, gasPrice, {
toNumericBase: 'hex',
multiplicandBase: 16,
@@ -47,9 +47,9 @@ function calcGasTotal (gasLimit, gasPrice) {
function isBalanceSufficient ({
amount = '0x0',
- amountConversionRate = 0,
- balance,
- conversionRate,
+ amountConversionRate = 1,
+ balance = '0x0',
+ conversionRate = 1,
gasTotal = '0x0',
primaryCurrency,
}) {
diff --git a/ui/app/components/sender-to-recipient.js b/ui/app/components/sender-to-recipient.js
deleted file mode 100644
index 9cef8e401..000000000
--- a/ui/app/components/sender-to-recipient.js
+++ /dev/null
@@ -1,72 +0,0 @@
-const { Component } = require('react')
-const h = require('react-hyperscript')
-const connect = require('react-redux').connect
-const PropTypes = require('prop-types')
-const Identicon = require('./identicon')
-
-class SenderToRecipient extends Component {
- renderRecipientIcon () {
- const { recipientAddress } = this.props
- return (
- recipientAddress
- ? h(Identicon, { address: recipientAddress, diameter: 20 })
- : h('i.fa.fa-file-text-o')
- )
- }
-
- renderRecipient () {
- const { recipientName } = this.props
- return (
- h('.sender-to-recipient__recipient', [
- this.renderRecipientIcon(),
- h(
- '.sender-to-recipient__name.sender-to-recipient__recipient-name',
- recipientName || this.context.t('newContract')
- ),
- ])
- )
- }
-
- render () {
- const { senderName, senderAddress } = this.props
-
- return (
- h('.sender-to-recipient__container', [
- h('.sender-to-recipient__sender', [
- h('.sender-to-recipient__sender-icon', [
- h(Identicon, {
- address: senderAddress,
- diameter: 20,
- }),
- ]),
- h('.sender-to-recipient__name.sender-to-recipient__sender-name', senderName),
- ]),
- h('.sender-to-recipient__arrow-container', [
- h('.sender-to-recipient__arrow-circle', [
- h('img', {
- height: 15,
- width: 15,
- src: './images/arrow-right.svg',
- }),
- ]),
- ]),
- this.renderRecipient(),
- ])
- )
- }
-}
-
-SenderToRecipient.propTypes = {
- senderName: PropTypes.string,
- senderAddress: PropTypes.string,
- recipientName: PropTypes.string,
- recipientAddress: PropTypes.string,
- t: PropTypes.func,
-}
-
-SenderToRecipient.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect()(SenderToRecipient)
-
diff --git a/ui/app/components/sender-to-recipient/index.js b/ui/app/components/sender-to-recipient/index.js
new file mode 100644
index 000000000..f515c4ac4
--- /dev/null
+++ b/ui/app/components/sender-to-recipient/index.js
@@ -0,0 +1 @@
+export { default } from './sender-to-recipient.component'
diff --git a/ui/app/components/sender-to-recipient/index.scss b/ui/app/components/sender-to-recipient/index.scss
new file mode 100644
index 000000000..a97393b8f
--- /dev/null
+++ b/ui/app/components/sender-to-recipient/index.scss
@@ -0,0 +1,74 @@
+.sender-to-recipient {
+ &__container {
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ border-bottom: 1px solid $geyser;
+ position: relative;
+ flex: 0 0 auto;
+ height: 42px;
+ }
+
+ &__tooltip-wrapper {
+ min-width: 0;
+ }
+
+ &__tooltip-container {
+ max-width: 100%;
+ }
+
+ &__sender,
+ &__recipient {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ flex: 1;
+ padding: 0 16px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &__sender {
+ padding-right: 30px;
+ cursor: pointer;
+ }
+
+ &__recipient {
+ padding-left: 30px;
+ border-left: 1px solid $geyser;
+
+ &--with-address {
+ cursor: pointer;
+ }
+ }
+
+ &__arrow-container {
+ position: absolute;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ &__arrow-circle {
+ background: $white;
+ padding: 5px;
+ border: 1px solid $geyser;
+ border-radius: 20px;
+ height: 32px;
+ width: 32px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ &__name {
+ padding-left: 14px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: .875rem;
+ }
+}
diff --git a/ui/app/components/sender-to-recipient/sender-to-recipient.component.js b/ui/app/components/sender-to-recipient/sender-to-recipient.component.js
new file mode 100644
index 000000000..cae173b56
--- /dev/null
+++ b/ui/app/components/sender-to-recipient/sender-to-recipient.component.js
@@ -0,0 +1,117 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import Identicon from '../identicon'
+import Tooltip from '../tooltip-v2'
+import copyToClipboard from 'copy-to-clipboard'
+
+export default class SenderToRecipient extends Component {
+ static propTypes = {
+ senderName: PropTypes.string,
+ senderAddress: PropTypes.string,
+ recipientName: PropTypes.string,
+ recipientAddress: PropTypes.string,
+ t: PropTypes.func,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ state = {
+ senderAddressCopied: false,
+ recipientAddressCopied: false,
+ }
+
+ renderRecipientWithAddress () {
+ const { t } = this.context
+ const { recipientName, recipientAddress } = this.props
+
+ return (
+ <div
+ className="sender-to-recipient__recipient sender-to-recipient__recipient--with-address"
+ onClick={() => {
+ this.setState({ recipientAddressCopied: true })
+ copyToClipboard(recipientAddress)
+ }}
+ >
+ <div className="sender-to-recipient__sender-icon">
+ <Identicon
+ address={recipientAddress}
+ diameter={24}
+ />
+ </div>
+ <Tooltip
+ position="bottom"
+ title={this.state.recipientAddressCopied ? t('copiedExclamation') : t('copyAddress')}
+ wrapperClassName="sender-to-recipient__tooltip-wrapper"
+ containerClassName="sender-to-recipient__tooltip-container"
+ onHidden={() => this.setState({ recipientAddressCopied: false })}
+ >
+ <div className="sender-to-recipient__name sender-to-recipient__recipient-name">
+ { recipientName || this.context.t('newContract') }
+ </div>
+ </Tooltip>
+ </div>
+ )
+ }
+
+ renderRecipientWithoutAddress () {
+ return (
+ <div className="sender-to-recipient__recipient">
+ <i className="fa fa-file-text-o" />
+ <div className="sender-to-recipient__name sender-to-recipient__recipient-name">
+ { this.context.t('newContract') }
+ </div>
+ </div>
+ )
+ }
+
+ render () {
+ const { t } = this.context
+ const { senderName, senderAddress, recipientAddress } = this.props
+
+ return (
+ <div className="sender-to-recipient__container">
+ <div
+ className="sender-to-recipient__sender"
+ onClick={() => {
+ this.setState({ senderAddressCopied: true })
+ copyToClipboard(senderAddress)
+ }}
+ >
+ <div className="sender-to-recipient__sender-icon">
+ <Identicon
+ address={senderAddress}
+ diameter={24}
+ />
+ </div>
+ <Tooltip
+ position="bottom"
+ title={this.state.senderAddressCopied ? t('copiedExclamation') : t('copyAddress')}
+ wrapperClassName="sender-to-recipient__tooltip-wrapper"
+ containerClassName="sender-to-recipient__tooltip-container"
+ onHidden={() => this.setState({ senderAddressCopied: false })}
+ >
+ <div className="sender-to-recipient__name sender-to-recipient__sender-name">
+ { senderName }
+ </div>
+ </Tooltip>
+ </div>
+ <div className="sender-to-recipient__arrow-container">
+ <div className="sender-to-recipient__arrow-circle">
+ <img
+ height={15}
+ width={15}
+ src="./images/arrow-right.svg"
+ />
+ </div>
+ </div>
+ {
+ recipientAddress
+ ? this.renderRecipientWithAddress()
+ : this.renderRecipientWithoutAddress()
+ }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/tabs/index.js b/ui/app/components/tabs/index.js
new file mode 100644
index 000000000..3a8d18248
--- /dev/null
+++ b/ui/app/components/tabs/index.js
@@ -0,0 +1,3 @@
+import Tabs from './tabs.component'
+import Tab from './tab'
+export { Tabs, Tab }
diff --git a/ui/app/components/tabs/index.scss b/ui/app/components/tabs/index.scss
new file mode 100644
index 000000000..a3b42f8e3
--- /dev/null
+++ b/ui/app/components/tabs/index.scss
@@ -0,0 +1,11 @@
+@import './tab/index';
+
+.tabs {
+ &__list {
+ display: flex;
+ justify-content: flex-start;
+ background-color: #f9fafa;
+ border-bottom: 1px solid $geyser;
+ padding: 0 16px;
+ }
+}
diff --git a/ui/app/components/tabs/tab/index.js b/ui/app/components/tabs/tab/index.js
new file mode 100644
index 000000000..fbc309e8e
--- /dev/null
+++ b/ui/app/components/tabs/tab/index.js
@@ -0,0 +1,2 @@
+import Tab from './tab.component'
+module.exports = Tab
diff --git a/ui/app/components/tabs/tab/index.scss b/ui/app/components/tabs/tab/index.scss
new file mode 100644
index 000000000..1de6ffa0e
--- /dev/null
+++ b/ui/app/components/tabs/tab/index.scss
@@ -0,0 +1,15 @@
+.tab {
+ color: #8C8E94;
+ font-size: .75rem;
+ text-transform: uppercase;
+ cursor: pointer;
+ padding: 8px 0;
+ margin: 0 8px;
+ min-width: 50px;
+ text-align: center;
+
+ &--active {
+ color: $black;
+ border-bottom: 2px solid $curious-blue;
+ }
+}
diff --git a/ui/app/components/tabs/tab/tab.component.js b/ui/app/components/tabs/tab/tab.component.js
new file mode 100644
index 000000000..a59da8904
--- /dev/null
+++ b/ui/app/components/tabs/tab/tab.component.js
@@ -0,0 +1,31 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+
+const Tab = props => {
+ const { name, onClick, isActive, tabIndex } = props
+
+ return (
+ <li
+ className={classnames(
+ 'tab',
+ isActive && 'tab--active',
+ )}
+ onClick={event => {
+ event.preventDefault()
+ onClick(tabIndex)
+ }}
+ >
+ { name }
+ </li>
+ )
+}
+
+Tab.propTypes = {
+ name: PropTypes.string.isRequired,
+ onClick: PropTypes.func,
+ isActive: PropTypes.bool,
+ tabIndex: PropTypes.number,
+}
+
+export default Tab
diff --git a/ui/app/components/tabs/tabs.component.js b/ui/app/components/tabs/tabs.component.js
new file mode 100644
index 000000000..d28f3695e
--- /dev/null
+++ b/ui/app/components/tabs/tabs.component.js
@@ -0,0 +1,63 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+
+export default class Tabs extends Component {
+ static propTypes = {
+ defaultActiveTabIndex: PropTypes.number,
+ children: PropTypes.node,
+ }
+
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ activeTabIndex: props.defaultActiveTabIndex || 0,
+ }
+ }
+
+ handleTabClick (tabIndex) {
+ const { activeTabIndex } = this.state
+
+ if (tabIndex !== activeTabIndex) {
+ this.setState({
+ activeTabIndex: tabIndex,
+ })
+ }
+ }
+
+ renderTabs () {
+ // const { children } = this.props
+ const numberOfTabs = React.Children.count(this.props.children)
+
+ return React.Children.map(this.props.children, (child, index) => {
+ return child && React.cloneElement(child, {
+ onClick: index => this.handleTabClick(index),
+ tabIndex: index,
+ isActive: numberOfTabs > 1 && index === this.state.activeTabIndex,
+ key: index,
+ })
+ })
+ }
+
+ renderActiveTabContent () {
+ const { children } = this.props
+ const { activeTabIndex } = this.state
+
+ return children[activeTabIndex]
+ ? children[activeTabIndex].props.children
+ : children.props.children
+ }
+
+ render () {
+ return (
+ <div className="tabs">
+ <ul className="tabs__list">
+ { this.renderTabs() }
+ </ul>
+ <div className="tabs__content">
+ { this.renderActiveTabContent() }
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/tooltip-v2.js b/ui/app/components/tooltip-v2.js
index 133a0f16a..05a5efc80 100644
--- a/ui/app/components/tooltip-v2.js
+++ b/ui/app/components/tooltip-v2.js
@@ -12,7 +12,7 @@ function Tooltip () {
Tooltip.prototype.render = function () {
const props = this.props
- const { position, title, children, wrapperClassName } = props
+ const { position, title, children, wrapperClassName, containerClassName, onHidden } = props
return h('div', {
className: wrapperClassName,
@@ -25,6 +25,8 @@ Tooltip.prototype.render = function () {
hideOnClick: false,
size: 'small',
arrow: true,
+ className: containerClassName,
+ onHidden,
}, children),
])