diff --git a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js
new file mode 100644
index 000000000000..53ff473e4b51
--- /dev/null
+++ b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js
@@ -0,0 +1,170 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Modal from '../../modal'
+import Identicon from '../../../ui/identicon'
+import TextField from '../../../ui/text-field'
+import classnames from 'classnames'
+
+export default class EditApprovalPermission extends PureComponent {
+ static propTypes = {
+ hideModal: PropTypes.func.isRequired,
+ selectedIdentity: PropTypes.object,
+ tokenAmount: PropTypes.string,
+ customTokenAmount: PropTypes.string,
+ tokenSymbol: PropTypes.string,
+ tokenBalance: PropTypes.string,
+ setCustomAmount: PropTypes.func,
+ origin: PropTypes.string,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ state = {
+ customSpendLimit: this.props.customTokenAmount,
+ selectedOptionIsUnlimited: !this.props.customTokenAmount,
+ }
+
+ renderModalContent () {
+ const { t } = this.context
+ const {
+ hideModal,
+ selectedIdentity,
+ tokenAmount,
+ tokenSymbol,
+ tokenBalance,
+ customTokenAmount,
+ origin,
+ } = this.props
+ const { name, address } = selectedIdentity || {}
+ const { selectedOptionIsUnlimited } = this.state
+
+ return (
+
+
+
+ { t('editPermission') }
+
+
hideModal()}
+ />
+
+
+
+
+
{ name }
+
{ t('balance') }
+
+
+ {`${tokenBalance} ${tokenSymbol}`}
+
+
+
+
+ { t('spendLimitPermission') }
+
+
+ { t('allowWithdrawAndSpend', [origin]) }
+
+
+
this.setState({ selectedOptionIsUnlimited: true })}
+ >
+
+
+ { selectedOptionIsUnlimited &&
}
+
+
+
+ {
+ tokenAmount < tokenBalance
+ ? t('proposedApprovalLimit')
+ : t('unlimited')
+ }
+
+
+ { t('spendLimitRequestedBy', [origin]) }
+
+
+ {`${tokenAmount} ${tokenSymbol}`}
+
+
+
+
+
this.setState({ selectedOptionIsUnlimited: false })}
+ >
+
+
+ { !selectedOptionIsUnlimited &&
}
+
+
+
+ { t('customSpendLimit') }
+
+
+ { t('enterMaxSpendLimit') }
+
+
+ {
+ this.setState({ customSpendLimit: event.target.value })
+ if (selectedOptionIsUnlimited) {
+ this.setState({ selectedOptionIsUnlimited: false })
+ }
+ }}
+ fullWidth
+ margin="dense"
+ value={ this.state.customSpendLimit }
+ />
+
+
+
+
+
+ )
+ }
+
+ render () {
+ const { t } = this.context
+ const { setCustomAmount, hideModal, customTokenAmount } = this.props
+ const { selectedOptionIsUnlimited, customSpendLimit } = this.state
+ return (
+
{
+ setCustomAmount(!selectedOptionIsUnlimited ? customSpendLimit : '')
+ hideModal()
+ }}
+ submitText={t('save')}
+ submitType="primary"
+ contentClass="edit-approval-permission-modal-content"
+ containerClass="edit-approval-permission-modal-container"
+ submitDisabled={ (customSpendLimit === customTokenAmount) && !selectedOptionIsUnlimited }
+ >
+ { this.renderModalContent() }
+
+ )
+ }
+}
diff --git a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.container.js b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.container.js
new file mode 100644
index 000000000000..ac25fa149587
--- /dev/null
+++ b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.container.js
@@ -0,0 +1,18 @@
+import { connect } from 'react-redux'
+import { compose } from 'recompose'
+import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
+import EditApprovalPermission from './edit-approval-permission.component'
+import { getSelectedIdentity } from '../../../../selectors/selectors'
+
+const mapStateToProps = (state) => {
+ const modalStateProps = state.appState.modal.modalState.props || {}
+ return {
+ selectedIdentity: getSelectedIdentity(state),
+ ...modalStateProps,
+ }
+}
+
+export default compose(
+ withModalProps,
+ connect(mapStateToProps)
+)(EditApprovalPermission)
diff --git a/ui/app/components/app/modals/edit-approval-permission/index.js b/ui/app/components/app/modals/edit-approval-permission/index.js
new file mode 100644
index 000000000000..3f50d3e9991e
--- /dev/null
+++ b/ui/app/components/app/modals/edit-approval-permission/index.js
@@ -0,0 +1 @@
+export { default } from './edit-approval-permission.container'
diff --git a/ui/app/components/app/modals/edit-approval-permission/index.scss b/ui/app/components/app/modals/edit-approval-permission/index.scss
new file mode 100644
index 000000000000..f400da4c1cf4
--- /dev/null
+++ b/ui/app/components/app/modals/edit-approval-permission/index.scss
@@ -0,0 +1,167 @@
+.edit-approval-permission {
+ width: 100%;
+
+ &__header,
+ &__account-info {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ border-bottom: 1px solid #d2d8dd;
+ }
+
+ &__header {
+ padding: 24px;
+
+ &__close {
+ position: absolute;
+ right: 24px;
+ background-image: url("/images/close-gray.svg");
+ width: .75rem;
+ height: .75rem;
+ cursor: pointer;
+ }
+ }
+
+ &__title {
+ font-weight: bold;
+ font-size: 18px;
+ line-height: 25px;
+ }
+
+ &__account-info {
+ justify-content: space-between;
+ padding: 8px 24px;
+
+ &__account,
+ &__balance {
+ font-weight: normal;
+ font-size: 14px;
+ color: #24292E;
+ }
+
+ &__account {
+ display: flex;
+ align-items: center;
+ }
+
+ &__name {
+ margin-left: 8px;
+ margin-right: 8px;
+ }
+
+ &__balance {
+ color: #6A737D;
+ }
+ }
+
+ &__edit-section {
+ padding: 24px;
+
+ &__title {
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 20px;
+ color: #24292E;
+ }
+
+ &__description {
+ font-weight: normal;
+ font-size: 12px;
+ line-height: 17px;
+ color: #6A737D;
+ margin-top: 8px;
+ }
+
+ &__option {
+ display: flex;
+ align-items: flex-start;
+ margin-top: 20px;
+ }
+
+ &__radio-button {
+ width: 18px;
+ }
+
+ &__option-text {
+ display: flex;
+ flex-direction: column;
+ }
+
+ &__option-label,
+ &__option-label--selected {
+ font-weight: normal;
+ font-size: 14px;
+ line-height: 20px;
+ color: #474B4D;
+ }
+
+ &__option-label--selected {
+ color: #037DD6;
+ }
+
+ &__option-description {
+ font-weight: normal;
+ font-size: 12px;
+ line-height: 17px;
+ color: #6A737D;
+ margin-top: 8px;
+ margin-bottom: 6px;
+ }
+
+ &__option-value {
+ font-weight: normal;
+ font-size: 18px;
+ line-height: 25px;
+ color: #24292E;
+ }
+
+ &__radio-button {
+ position: relative;
+ width: 18px;
+ height: 18px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-right: 4px;
+ }
+
+ &__radio-button-outline,
+ &__radio-button-outline--selected {
+ width: 18px;
+ height: 18px;
+ background: #DADCDD;
+ border-radius: 9px;
+ position: absolute;
+ }
+
+ &__radio-button-outline--selected {
+ background: #037DD6;
+ }
+
+ &__radio-button-fill {
+ width: 14px;
+ height: 14px;
+ background: white;
+ border-radius: 7px;
+ position: absolute;
+ }
+
+ &__radio-button-dot {
+ width: 8px;
+ height: 8px;
+ background: #037DD6;
+ border-radius: 4px;
+ position: absolute;
+ }
+ }
+}
+
+.edit-approval-permission-modal-content {
+ padding: 0px;
+}
+
+.edit-approval-permission-modal-container {
+ max-height: 550px;
+ width: 100%;
+}
diff --git a/ui/app/components/app/modals/index.scss b/ui/app/components/app/modals/index.scss
index d93a41140a73..da7a27b84086 100644
--- a/ui/app/components/app/modals/index.scss
+++ b/ui/app/components/app/modals/index.scss
@@ -9,3 +9,5 @@
@import 'metametrics-opt-in-modal/index';
@import './add-to-addressbook-modal/index';
+
+@import './edit-approval-permission/index';
diff --git a/ui/app/components/app/modals/modal.js b/ui/app/components/app/modals/modal.js
index c901d6db8137..ada758b9913d 100644
--- a/ui/app/components/app/modals/modal.js
+++ b/ui/app/components/app/modals/modal.js
@@ -28,6 +28,7 @@ import ClearApprovedOrigins from './clear-approved-origins'
import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container'
import ConfirmDeleteNetwork from './confirm-delete-network'
import AddToAddressBookModal from './add-to-addressbook-modal'
+import EditApprovalPermission from './edit-approval-permission'
const modalContainerBaseStyle = {
transform: 'translate3d(-50%, 0, 0px)',
@@ -304,6 +305,31 @@ const MODALS = {
},
},
+ EDIT_APPROVAL_PERMISSION: {
+ contents: h(EditApprovalPermission),
+ mobileModalStyle: {
+ width: '95vw',
+ height: '100vh',
+ top: '50px',
+ transform: 'none',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ },
+ laptopModalStyle: {
+ width: 'auto',
+ height: '0px',
+ top: '80px',
+ left: '0px',
+ transform: 'none',
+ margin: '0 auto',
+ position: 'relative',
+ },
+ contentStyle: {
+ borderRadius: '8px',
+ },
+ },
+
TRANSACTION_CONFIRMED: {
disableBackdropClick: true,
contents: h(TransactionConfirmed),
diff --git a/ui/app/css/itcss/tools/utilities.scss b/ui/app/css/itcss/tools/utilities.scss
index 209614c6bc78..81eb18d06ca4 100644
--- a/ui/app/css/itcss/tools/utilities.scss
+++ b/ui/app/css/itcss/tools/utilities.scss
@@ -141,11 +141,11 @@
}
.cursor-pointer:hover {
- transform: scale(1.1);
+ transform: scale(1.05);
}
.cursor-pointer:active {
- transform: scale(.95);
+ transform: scale(.97);
}
.cursor-disabled {
diff --git a/ui/app/helpers/higher-order-components/with-token-tracker/with-token-tracker.component.js b/ui/app/helpers/higher-order-components/with-token-tracker/with-token-tracker.component.js
index 36f6a6efdd0e..8025dd5bc4c0 100644
--- a/ui/app/helpers/higher-order-components/with-token-tracker/with-token-tracker.component.js
+++ b/ui/app/helpers/higher-order-components/with-token-tracker/with-token-tracker.component.js
@@ -15,6 +15,7 @@ export default function withTokenTracker (WrappedComponent) {
this.state = {
string: '',
symbol: '',
+ balance: '',
error: null,
}
@@ -78,8 +79,8 @@ export default function withTokenTracker (WrappedComponent) {
if (!this.tracker.running) {
return
}
- const [{ string, symbol }] = tokens
- this.setState({ string, symbol, error: null })
+ const [{ string, symbol, balance }] = tokens
+ this.setState({ string, symbol, error: null, balance })
}
removeListeners () {
@@ -91,13 +92,13 @@ export default function withTokenTracker (WrappedComponent) {
}
render () {
- const { string, symbol, error } = this.state
-
+ const { balance, string, symbol, error } = this.state
return (
)
diff --git a/ui/app/helpers/utils/token-util.js b/ui/app/helpers/utils/token-util.js
index 831d851316aa..2c4f67fd0b9d 100644
--- a/ui/app/helpers/utils/token-util.js
+++ b/ui/app/helpers/utils/token-util.js
@@ -128,6 +128,11 @@ export function calcTokenAmount (value, decimals) {
return new BigNumber(String(value)).div(multiplier)
}
+export function calcTokenValue (value, decimals) {
+ const multiplier = Math.pow(10, Number(decimals || 0))
+ return new BigNumber(String(value)).times(multiplier)
+}
+
export function getTokenValue (tokenParams = []) {
const valueData = tokenParams.find(param => param.name === '_value')
return valueData && valueData.value
diff --git a/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js b/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js
new file mode 100644
index 000000000000..9f11fbb2fb28
--- /dev/null
+++ b/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js
@@ -0,0 +1,223 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import Identicon from '../../../components/ui/identicon'
+import {
+ addressSummary,
+} from '../../../helpers/utils/util'
+
+export default class ConfirmApproveContent extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ amount: PropTypes.string,
+ txFeeTotal: PropTypes.string,
+ tokenAmount: PropTypes.string,
+ customTokenAmount: PropTypes.string,
+ tokenSymbol: PropTypes.string,
+ siteImage: PropTypes.string,
+ tokenAddress: PropTypes.string,
+ showCustomizeGasModal: PropTypes.func,
+ showEditApprovalPermissionModal: PropTypes.func,
+ origin: PropTypes.string,
+ setCustomAmount: PropTypes.func,
+ tokenBalance: PropTypes.string,
+ data: PropTypes.string,
+ toAddress: PropTypes.string,
+ fiatTransactionTotal: PropTypes.string,
+ ethTransactionTotal: PropTypes.string,
+ }
+
+ state = {
+ showFullTxDetails: false,
+ }
+
+ renderApproveContentCard ({
+ symbol,
+ title,
+ showEdit,
+ onEditClick,
+ content,
+ footer,
+ noBorder,
+ }) {
+ return (
+
+
+
{ symbol }
+
{ title }
+ { showEdit &&
onEditClick()}
+ >Edit
}
+
+
+ { content }
+
+ { footer }
+
+ )
+ }
+
+ // TODO: Add "Learn Why" with link to the feeAssociatedRequest text
+ renderTransactionDetailsContent () {
+ const { t } = this.context
+ const {
+ ethTransactionTotal,
+ fiatTransactionTotal,
+ } = this.props
+ return (
+
+
+ { t('feeAssociatedRequest') }
+
+
+
+ { fiatTransactionTotal }
+
+
+ { ethTransactionTotal }
+
+
+
+ )
+ }
+
+ renderPermissionContent () {
+ const { t } = this.context
+ const { customTokenAmount, tokenAmount, tokenSymbol, origin, toAddress } = this.props
+
+ return (
+
+
{ t('accessAndSpendNotice', [origin]) }
+
+
{ t('amountWithColon') }
+
{ `${customTokenAmount || tokenAmount} ${tokenSymbol}` }
+
+
+
{ t('toWithColon') }
+
{ addressSummary(toAddress) }
+
+
+ )
+ }
+
+ renderDataContent () {
+ const { t } = this.context
+ const { data } = this.props
+ return (
+
+
{ t('functionApprove') }
+
{ data }
+
+ )
+ }
+
+ render () {
+ const { t } = this.context
+ const {
+ siteImage,
+ tokenAmount,
+ customTokenAmount,
+ origin,
+ tokenSymbol,
+ showCustomizeGasModal,
+ showEditApprovalPermissionModal,
+ setCustomAmount,
+ tokenBalance,
+ } = this.props
+ const { showFullTxDetails } = this.state
+
+ return (
+
+
+
+
+
+ { t('allowOriginSpendToken', [origin, tokenSymbol]) }
+
+
+ { t('trustSiteApprovePermission', [origin, tokenSymbol]) }
+
+
+
showEditApprovalPermissionModal({ customTokenAmount, tokenAmount, tokenSymbol, setCustomAmount, tokenBalance, origin })}
+ >
+ { t('editPermission') }
+
+
+
+ {this.renderApproveContentCard({
+ symbol:
,
+ title: 'Transaction Fee',
+ showEdit: true,
+ onEditClick: showCustomizeGasModal,
+ content: this.renderTransactionDetailsContent(),
+ noBorder: !showFullTxDetails,
+ footer:
this.setState({ showFullTxDetails: !this.state.showFullTxDetails })}
+ >
+
+
+ View full transaction details
+
+
+
+
,
+ })}
+
+
+ {
+ showFullTxDetails
+ ? (
+
+
+ {this.renderApproveContentCard({
+ symbol:
,
+ title: 'Permission',
+ content: this.renderPermissionContent(),
+ showEdit: true,
+ onEditClick: () => showEditApprovalPermissionModal({
+ customTokenAmount,
+ tokenAmount,
+ tokenSymbol,
+ tokenBalance,
+ setCustomAmount,
+ }),
+ })}
+
+
+ {this.renderApproveContentCard({
+ symbol: ,
+ title: 'Data',
+ content: this.renderDataContent(),
+ noBorder: true,
+ })}
+
+
+ )
+ : null
+ }
+
+ )
+ }
+}
diff --git a/ui/app/pages/confirm-approve/confirm-approve-content/index.js b/ui/app/pages/confirm-approve/confirm-approve-content/index.js
new file mode 100644
index 000000000000..8f225387a260
--- /dev/null
+++ b/ui/app/pages/confirm-approve/confirm-approve-content/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-approve-content.component'
diff --git a/ui/app/pages/confirm-approve/confirm-approve-content/index.scss b/ui/app/pages/confirm-approve/confirm-approve-content/index.scss
new file mode 100644
index 000000000000..7d3018f6e622
--- /dev/null
+++ b/ui/app/pages/confirm-approve/confirm-approve-content/index.scss
@@ -0,0 +1,306 @@
+.confirm-approve-content {
+ display: flex;
+ flex-flow: column;
+ align-items: center;
+ width: 100%;
+ height: 100%;
+
+ font-family: Roboto;
+ font-style: normal;
+
+ &__identicon-wrapper {
+ display: flex;
+ width: 100%;
+ justify-content: center;
+ margin-top: 22px;
+ padding-left: 24px;
+ padding-right: 24px;
+ }
+
+ &__full-tx-content {
+ display: flex;
+ flex-flow: column;
+ align-items: center;
+ width: 390px;
+ font-family: Roboto;
+ font-style: normal;
+ padding-left: 24px;
+ padding-right: 24px;
+ }
+
+ &__card-wrapper {
+ width: 100%;
+ }
+
+ &__title {
+ font-weight: normal;
+ font-size: 24px;
+ line-height: 34px;
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ text-align: center;
+ margin-top: 22px;
+ padding-left: 24px;
+ padding-right: 24px;
+ }
+
+ &__description {
+ font-weight: normal;
+ font-size: 14px;
+ line-height: 20px;
+ margin-top: 16px;
+ margin-bottom: 16px;
+ color: #6A737D;
+ text-align: center;
+ padding-left: 24px;
+ padding-right: 24px;
+ }
+
+ &__card,
+ &__card--no-border {
+ display: flex;
+ flex-flow: column;
+ border-bottom: 1px solid #D2D8DD;
+ position: relative;
+ padding-left: 24px;
+ padding-right: 24px;
+
+ &__bold-text {
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 20px;
+ }
+
+ &__thin-text {
+ font-weight: normal;
+ font-size: 12px;
+ line-height: 17px;
+ color: #6A737D;
+ }
+ }
+
+ &__card--no-border {
+ border-bottom: none;
+ }
+
+ &__card-header {
+ display: flex;
+ flex-flow: row;
+ margin-top: 20px;
+ align-items: center;
+ position: relative;
+
+ &__symbol {
+ width: auto;
+ }
+
+ &__symbol--aligned {
+ width: 100%;
+ }
+
+ &__title, &__title-value {
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 20px;
+ }
+
+ &__title {
+ width: 100%;
+ margin-left: 16px;
+ }
+
+ &__title--aligned {
+ margin-left: 27px;
+ position: absolute;
+ width: auto;
+ }
+ }
+
+ &__card-content {
+ margin-top: 6px;
+ margin-bottom: 12px;
+ }
+
+ &__card-content--aligned {
+ margin-left: 42px;
+ }
+
+ &__transaction-total-symbol {
+ width: 16px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 16px;
+
+ &__x {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ div {
+ width: 22px;
+ height: 2px;
+ background: #037DD6;
+ position: absolute;
+ }
+
+ div:first-of-type {
+ transform: rotate(45deg);
+ }
+
+ div:last-of-type {
+ transform: rotate(-45deg);
+ }
+ }
+
+ &__circle {
+ width: 14px;
+ height: 14px;
+ border: 2px solid #037DD6;
+ border-radius: 50%;
+ background: white;
+ position: absolute;
+ }
+ }
+
+ &__transaction-details-content {
+ display: flex;
+ flex-flow: row;
+ justify-content: space-between;
+
+ .confirm-approve-content__small-text {
+ width: 160px;
+ }
+
+ &__fee {
+ display: flex;
+ flex-flow: column;
+ align-items: flex-end;
+ text-align: right;
+ }
+
+ &__primary-fee {
+ font-weight: bold;
+ font-size: 18px;
+ line-height: 25px;
+ color: #000000;
+ }
+
+ &__secondary-fee {
+ font-weight: normal;
+ font-size: 14px;
+ line-height: 20px;
+ color: #8C8E94;
+ }
+ }
+
+ &__view-full-tx-button-wrapper {
+ display: flex;
+ flex-flow: row;
+ margin-bottom: 16px;
+ justify-content: center;
+
+ i {
+ margin-left: 6px;
+ display: flex;
+ color: #3099f2;
+ align-items: center;
+ }
+ }
+
+ &__view-full-tx-button {
+ display: flex;
+ flex-flow: row;
+ }
+
+ &__edit-submission-button-container {
+ display: flex;
+ flex-flow: row;
+ padding-top: 15px;
+ padding-bottom: 30px;
+ border-bottom: 1px solid #D2D8DD;
+ width: 100%;
+ justify-content: center;
+ padding-left: 24px;
+ padding-right: 24px;
+ }
+
+ &__large-text {
+ font-size: 18px;
+ line-height: 25px;
+ color: #24292E;
+ }
+
+ &__medium-link-text {
+ font-size: 14px;
+ line-height: 20px;
+ font-weight: 500;
+ color: #037DD6;
+ }
+
+ &__medium-text,
+ &__label {
+ font-weight: normal;
+ font-size: 14px;
+ line-height: 20px;
+ color: #24292E;
+ }
+
+ &__label {
+ font-weight: bold;
+ margin-right: 4px;
+ }
+
+ &__small-text, &__small-blue-text, &__info-row {
+ font-weight: normal;
+ font-size: 12px;
+ line-height: 17px;
+ color: #6A737D;
+ }
+
+ &__small-blue-text {
+ color: #037DD6;
+ }
+
+ &__info-row {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 6px;
+ }
+
+ &__data,
+ &__permission {
+ width: 100%;
+ }
+
+ &__permission {
+ .flex-row {
+ margin-top: 14px;
+ }
+ }
+
+ &__data {
+ &__data-block {
+ overflow-wrap: break-word;
+ margin-right: 16px;
+ margin-top: 12px;
+ }
+ }
+
+ &__footer {
+ display: flex;
+ align-items: flex-end;
+ margin-top: 16px;
+ padding-left: 34px;
+ padding-right: 24px;
+
+ .confirm-approve-content__small-text {
+ margin-left: 16px;
+ }
+ }
+}
+
+.confirm-approve-content--full {
+ height: auto;
+}
diff --git a/ui/app/pages/confirm-approve/confirm-approve.component.js b/ui/app/pages/confirm-approve/confirm-approve.component.js
index b71eaa1d4804..2a40cfa96005 100644
--- a/ui/app/pages/confirm-approve/confirm-approve.component.js
+++ b/ui/app/pages/confirm-approve/confirm-approve.component.js
@@ -1,20 +1,109 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import ConfirmTokenTransactionBase from '../confirm-token-transaction-base'
+import ConfirmTransactionBase from '../confirm-transaction-base'
+import ConfirmApproveContent from './confirm-approve-content'
+import { getCustomTxParamsData } from './confirm-approve.util'
+import {
+ calcTokenAmount,
+} from '../../helpers/utils/token-util'
export default class ConfirmApprove extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
static propTypes = {
+ tokenAddress: PropTypes.string,
+ toAddress: PropTypes.string,
tokenAmount: PropTypes.number,
tokenSymbol: PropTypes.string,
+ fiatTransactionTotal: PropTypes.string,
+ ethTransactionTotal: PropTypes.string,
+ contractExchangeRate: PropTypes.number,
+ conversionRate: PropTypes.number,
+ currentCurrency: PropTypes.string,
+ showCustomizeGasModal: PropTypes.func,
+ showEditApprovalPermissionModal: PropTypes.func,
+ origin: PropTypes.string,
+ siteImage: PropTypes.string,
+ tokenTrackerBalance: PropTypes.string,
+ data: PropTypes.string,
+ decimals: PropTypes.number,
+ txData: PropTypes.object,
+ }
+
+ static defaultProps = {
+ tokenAmount: 0,
+ }
+
+ state = {
+ customPermissionAmount: '',
+ }
+
+ componentDidUpdate (prevProps) {
+ const { tokenAmount } = this.props
+
+ if (tokenAmount !== prevProps.tokenAmount) {
+ this.setState({ customPermissionAmount: tokenAmount })
+ }
}
render () {
- const { tokenAmount, tokenSymbol } = this.props
+ const {
+ toAddress,
+ tokenAddress,
+ tokenSymbol,
+ tokenAmount,
+ showCustomizeGasModal,
+ showEditApprovalPermissionModal,
+ origin,
+ siteImage,
+ tokenTrackerBalance,
+ data,
+ decimals,
+ txData,
+ ethTransactionTotal,
+ fiatTransactionTotal,
+ ...restProps
+ } = this.props
+ const { customPermissionAmount } = this.state
+
+ const tokensText = `${tokenAmount} ${tokenSymbol}`
+
+ const tokenBalance = tokenTrackerBalance
+ ? Number(calcTokenAmount(tokenTrackerBalance, decimals)).toPrecision(9)
+ : ''
return (
-
{
+ this.setState({ customPermissionAmount: newAmount })
+ }}
+ customTokenAmount={String(customPermissionAmount)}
+ tokenAmount={String(tokenAmount)}
+ origin={origin}
+ tokenSymbol={tokenSymbol}
+ tokenBalance={tokenBalance}
+ showCustomizeGasModal={() => showCustomizeGasModal(txData)}
+ showEditApprovalPermissionModal={showEditApprovalPermissionModal}
+ data={data}
+ toAddress={toAddress}
+ ethTransactionTotal={ethTransactionTotal}
+ fiatTransactionTotal={fiatTransactionTotal}
+ />}
+ hideSenderToRecipient={true}
+ customTxParamsData={customPermissionAmount
+ ? getCustomTxParamsData(data, { customPermissionAmount, tokenAmount, decimals })
+ : null
+ }
+ {...restProps}
/>
)
}
diff --git a/ui/app/pages/confirm-approve/confirm-approve.container.js b/ui/app/pages/confirm-approve/confirm-approve.container.js
index 5f8bb8f0b3ea..43f5aab9087b 100644
--- a/ui/app/pages/confirm-approve/confirm-approve.container.js
+++ b/ui/app/pages/confirm-approve/confirm-approve.container.js
@@ -1,15 +1,102 @@
import { connect } from 'react-redux'
+import { compose } from 'recompose'
+import { withRouter } from 'react-router-dom'
+import {
+ contractExchangeRateSelector,
+ transactionFeeSelector,
+} from '../../selectors/confirm-transaction'
+import { showModal } from '../../store/actions'
+import { tokenSelector } from '../../selectors/tokens'
+import {
+ getTokenData,
+} from '../../helpers/utils/transactions.util'
+import withTokenTracker from '../../helpers/higher-order-components/with-token-tracker'
+import {
+ calcTokenAmount,
+ getTokenToAddress,
+ getTokenValue,
+} from '../../helpers/utils/token-util'
import ConfirmApprove from './confirm-approve.component'
-import { approveTokenAmountAndToAddressSelector } from '../../selectors/confirm-transaction'
-const mapStateToProps = state => {
- const { confirmTransaction: { tokenProps: { tokenSymbol } = {} } } = state
- const { tokenAmount } = approveTokenAmountAndToAddressSelector(state)
+const mapStateToProps = (state, ownProps) => {
+ const { match: { params = {} } } = ownProps
+ const { id: paramsTransactionId } = params
+ const {
+ confirmTransaction,
+ metamask: { currentCurrency, conversionRate, selectedAddressTxList, approvedOrigins, selectedAddress },
+ } = state
+ const {
+ txData: { id: transactionId, txParams: { to: tokenAddress, data } = {} } = {},
+ } = confirmTransaction
+
+ const transaction = selectedAddressTxList.find(({ id }) => id === (Number(paramsTransactionId) || transactionId)) || {}
+
+ const {
+ ethTransactionTotal,
+ fiatTransactionTotal,
+ } = transactionFeeSelector(state, transaction)
+ const tokens = tokenSelector(state)
+ const currentToken = tokens && tokens.find(({ address }) => tokenAddress === address)
+ const { decimals, symbol: tokenSymbol } = currentToken || {}
+
+ const tokenData = getTokenData(data)
+ const tokenValue = tokenData && getTokenValue(tokenData.params)
+ const toAddress = tokenData && getTokenToAddress(tokenData.params)
+ const tokenAmount = tokenData && calcTokenAmount(tokenValue, decimals).toNumber()
+ const contractExchangeRate = contractExchangeRateSelector(state)
+
+ const { origin } = transaction
+ const formattedOrigin = origin
+ ? origin[0].toUpperCase() + origin.slice(1)
+ : ''
+
+ const { siteImage } = approvedOrigins[origin] || {}
return {
+ toAddress,
+ tokenAddress,
tokenAmount,
+ currentCurrency,
+ conversionRate,
+ contractExchangeRate,
+ fiatTransactionTotal,
+ ethTransactionTotal,
tokenSymbol,
+ siteImage,
+ token: { address: tokenAddress },
+ userAddress: selectedAddress,
+ origin: formattedOrigin,
+ data,
+ decimals: Number(decimals),
+ txData: transaction,
}
}
-export default connect(mapStateToProps)(ConfirmApprove)
+const mapDispatchToProps = (dispatch) => {
+ return {
+ showCustomizeGasModal: (txData) => dispatch(showModal({ name: 'CUSTOMIZE_GAS', txData })),
+ showEditApprovalPermissionModal: ({
+ tokenAmount,
+ customTokenAmount,
+ tokenSymbol,
+ tokenBalance,
+ setCustomAmount,
+ origin,
+ }) => dispatch(showModal({
+ name: 'EDIT_APPROVAL_PERMISSION',
+ tokenAmount,
+ customTokenAmount,
+ tokenSymbol,
+ tokenBalance,
+ setCustomAmount,
+ origin,
+ })),
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps),
+ withTokenTracker,
+)(ConfirmApprove)
+
diff --git a/ui/app/pages/confirm-approve/confirm-approve.util.js b/ui/app/pages/confirm-approve/confirm-approve.util.js
new file mode 100644
index 000000000000..be77c65f913a
--- /dev/null
+++ b/ui/app/pages/confirm-approve/confirm-approve.util.js
@@ -0,0 +1,28 @@
+import { decimalToHex } from '../../helpers/utils/conversions.util'
+import { calcTokenValue } from '../../helpers/utils/token-util.js'
+
+export function getCustomTxParamsData (data, { customPermissionAmount, tokenAmount, decimals }) {
+ if (customPermissionAmount) {
+ const tokenValue = decimalToHex(calcTokenValue(tokenAmount, decimals))
+
+ const re = new RegExp('(^.+)' + tokenValue + '$')
+ const matches = re.exec(data)
+
+ if (!matches || !matches[1]) {
+ return data
+ }
+ let dataWithoutCurrentAmount = matches[1]
+ const customPermissionValue = decimalToHex(calcTokenValue(Number(customPermissionAmount), decimals))
+
+ const differenceInLengths = customPermissionValue.length - tokenValue.length
+ const zeroModifier = dataWithoutCurrentAmount.length - differenceInLengths
+ if (differenceInLengths > 0) {
+ dataWithoutCurrentAmount = dataWithoutCurrentAmount.slice(0, zeroModifier)
+ } else if (differenceInLengths < 0) {
+ dataWithoutCurrentAmount = dataWithoutCurrentAmount.padEnd(zeroModifier, 0)
+ }
+
+ const customTxParamsData = dataWithoutCurrentAmount + customPermissionValue
+ return customTxParamsData
+ }
+}
diff --git a/ui/app/pages/confirm-approve/index.scss b/ui/app/pages/confirm-approve/index.scss
new file mode 100644
index 000000000000..18d7c29e82e7
--- /dev/null
+++ b/ui/app/pages/confirm-approve/index.scss
@@ -0,0 +1 @@
+@import 'confirm-approve-content/index';
diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
index 25725381d600..0b46fe9c95bd 100644
--- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
+++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
@@ -105,6 +105,8 @@ export default class ConfirmTransactionBase extends Component {
getNextNonce: PropTypes.func,
nextNonce: PropTypes.number,
tryReverseResolveAddress: PropTypes.func.isRequired,
+ hideSenderToRecipient: PropTypes.bool,
+ showAccountInHeader: PropTypes.bool,
}
state = {
@@ -645,6 +647,8 @@ export default class ConfirmTransactionBase extends Component {
warning,
unapprovedTxCount,
transactionCategory,
+ hideSenderToRecipient,
+ showAccountInHeader,
} = this.props
const { submitting, submitError, submitWarning } = this.state
@@ -655,6 +659,7 @@ export default class ConfirmTransactionBase extends Component {
this.handleCancelAll()}
onCancel={() => this.handleCancel()}
onSubmit={() => this.handleSubmit()}
+ hideSenderToRecipient={hideSenderToRecipient}
/>
)
}
diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
index 19602611cab8..9a238e780b1e 100644
--- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
+++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
@@ -46,7 +46,7 @@ const customNonceMerge = txData => customNonceValue ? ({
}) : txData
const mapStateToProps = (state, ownProps) => {
- const { toAddress: propsToAddress, match: { params = {} } } = ownProps
+ const { toAddress: propsToAddress, customTxParamsData, match: { params = {} } } = ownProps
const { id: paramsTransactionId } = params
const { showFiatInTestnets } = preferencesSelector(state)
const isMainnet = getIsMainnet(state)
@@ -133,6 +133,17 @@ const mapStateToProps = (state, ownProps) => {
const methodData = getKnownMethodData(state, data) || {}
+ let fullTxData = { ...txData, ...transaction }
+ if (customTxParamsData) {
+ fullTxData = {
+ ...fullTxData,
+ txParams: {
+ ...fullTxData.txParams,
+ data: customTxParamsData,
+ },
+ }
+ }
+
return {
balance,
fromAddress,
@@ -150,7 +161,7 @@ const mapStateToProps = (state, ownProps) => {
hexTransactionAmount,
hexTransactionFee,
hexTransactionTotal,
- txData: { ...txData, ...transaction },
+ txData: fullTxData,
tokenData,
methodData,
tokenProps,
diff --git a/ui/app/pages/index.scss b/ui/app/pages/index.scss
index e7242392b8a5..d79b7c28ddff 100644
--- a/ui/app/pages/index.scss
+++ b/ui/app/pages/index.scss
@@ -11,3 +11,5 @@
@import 'first-time-flow/index';
@import 'keychains/index';
+
+@import 'confirm-approve/index';