diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 0fe8e81cd..6e7df3aa8 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -80,12 +80,18 @@ "addAcquiredTokens": { "message": "Add the tokens you've acquired using MetaMask" }, + "advanced": { + "message": "Advanced" + }, "amount": { "message": "Amount" }, "amountPlusGas": { "message": "Amount + Gas" }, + "amountPlusTxFee": { + "message": "Amount + TX Fee" + }, "appDescription": { "message": "Ethereum Browser Extension", "description": "The description of the application" @@ -127,6 +133,9 @@ "balanceIsInsufficientGas": { "message": "Insufficient balance for current gas total" }, + "basic": { + "message": "Basic" + }, "beta": { "message": "BETA" }, @@ -303,6 +312,9 @@ "customGas": { "message": "Customize Gas" }, + "customGasSubTitle": { + "message": "Increasing fee may decrease processing times, but it is not guaranteed." + }, "customToken": { "message": "Custom Token" }, @@ -755,6 +767,9 @@ "optionalNickname": { "message": "Nickname (optional)" }, + "newTotal": { + "message": "New Total" + }, "next": { "message": "Next" }, @@ -820,6 +835,9 @@ "parameters": { "message": "Parameters" }, + "originalTotal": { + "message": "Original Total" + }, "password": { "message": "Password" }, diff --git a/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js b/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js new file mode 100644 index 000000000..dfc011c1e --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js @@ -0,0 +1,79 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import PageContainer from '../../page-container' +import { Tabs, Tab } from '../../tabs' + +export default class GasModalPageContainer extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + hideModal: PropTypes.func, + } + + state = {} + + renderBasicTabContent () { + return ( +
+ ) + } + + renderAdvancedTabContent () { + return ( +
+ ) + } + + renderInfoRow (className, totalLabelKey, fiatTotal, cryptoTotal) { + return ( +
+
+ {`${this.context.t(totalLabelKey)}:`}{fiatTotal} +
+
+ {`${this.context.t('amountPlusTxFee')}`}{cryptoTotal} +
+
+ ) + } + + renderTabContent (mainTabContent) { + return ( +
+ { mainTabContent() } + { this.renderInfoRow('info-row--fade', 'originalTotal', '$20.02 USD', '0.06685 ETH') } + { this.renderInfoRow('info-row', 'newTotal', '$20.02 USD', '0.06685 ETH') } +
+ ) + } + + renderTabs () { + return ( + + + { this.renderTabContent(this.renderBasicTabContent) } + + + { this.renderTabContent(this.renderAdvancedTabContent) } + + + ) + } + + render () { + const { hideModal } = this.props + + return ( + hideModal()} + onClose={() => hideModal()} + /> + ) + } +} diff --git a/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js b/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js new file mode 100644 index 000000000..71c700507 --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js @@ -0,0 +1,11 @@ +import { connect } from 'react-redux' +import GasModalPageContainer from './gas-modal-page-container.component' +import { hideModal } from '../../../actions' + +const mapDispatchToProps = dispatch => { + return { + hideModal: () => dispatch(hideModal()), + } +} + +export default connect(null, mapDispatchToProps)(GasModalPageContainer) diff --git a/ui/app/components/gas-customization/gas-modal-page-container/index.js b/ui/app/components/gas-customization/gas-modal-page-container/index.js new file mode 100644 index 000000000..ec0ebad22 --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/index.js @@ -0,0 +1 @@ +export { default } from './gas-modal-page-container.container' diff --git a/ui/app/components/gas-customization/gas-modal-page-container/index.scss b/ui/app/components/gas-customization/gas-modal-page-container/index.scss new file mode 100644 index 000000000..2d2250212 --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/index.scss @@ -0,0 +1,50 @@ +.gas-modal-content { + .basic-tab { + height: 219px; + } + + .advanced-tab { + height: 475px; + } + + .info-row, .info-row--fade { + width: 100%; + background: $polar; + padding: 15px 21px; + display: flex; + flex-flow: column; + color: $scorpion; + + .total-info, .sum-info { + display: flex; + flex-flow: row; + justify-content: space-between; + } + + .total-info { + .total-label { + font-size: 16px; + } + + .total-value { + font-size: 16px; + font-weight: bold; + } + } + + .sum-info { + .sum-label { + font-size: 12px; + } + + .sum-value { + font-size: 14px; + } + } + } + + .info-row--fade { + background: white; + color: $dusty-gray; + } +} \ No newline at end of file diff --git a/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js b/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js new file mode 100644 index 000000000..ffe242de2 --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js @@ -0,0 +1,156 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import GasModalPageContainer from '../gas-modal-page-container.component.js' + +import PageContainer from '../../../page-container' +import { Tab } from '../../../tabs' + +const propsMethodSpies = { + hideModal: sinon.spy(), +} + +describe('GasModalPageContainer Component', function () { + let wrapper + + beforeEach(() => { + wrapper = shallow(, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }) + }) + + afterEach(() => { + propsMethodSpies.hideModal.resetHistory() + }) + + describe('render', () => { + it('should render a PageContainer compenent', () => { + assert.equal(wrapper.find(PageContainer).length, 1) + }) + + it('should pass correct props to PageContainer', () => { + const { + title, + subtitle, + disabled, + } = wrapper.find(PageContainer).props() + assert.equal(title, 'customGas') + assert.equal(subtitle, 'customGasSpeedUp') + assert.equal(disabled, false) + }) + + it('should pass the correct onCancel and onClose methods to PageContainer', () => { + const { + onCancel, + onClose, + } = wrapper.find(PageContainer).props() + assert.equal(propsMethodSpies.hideModal.callCount, 0) + onCancel() + assert.equal(propsMethodSpies.hideModal.callCount, 1) + onClose() + assert.equal(propsMethodSpies.hideModal.callCount, 2) + }) + + it('should pass the correct renderTabs property to PageContainer', () => { + sinon.stub(GasModalPageContainer.prototype, 'renderTabs').returns('mockTabs') + const renderTabsWrapperTester = shallow(, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }) + const { tabsComponent } = renderTabsWrapperTester.find(PageContainer).props() + assert.equal(tabsComponent, 'mockTabs') + GasModalPageContainer.prototype.renderTabs.restore() + }) + }) + + describe('renderTabs', () => { + it('should render a Tabs component with "Basic" and "Advanced" tabs', () => { + const renderTabsResult = wrapper.instance().renderTabs() + const renderedTabs = shallow(renderTabsResult) + assert.equal(renderedTabs.props().className, 'tabs') + + const tabs = renderedTabs.find(Tab) + assert.equal(tabs.length, 2) + + assert.equal(tabs.at(0).props().name, 'basic') + assert.equal(tabs.at(1).props().name, 'advanced') + + assert.equal(tabs.at(0).childAt(0).props().className, 'gas-modal-content') + assert.equal(tabs.at(1).childAt(0).props().className, 'gas-modal-content') + }) + + it('should render the expected children of each tab', () => { + const GP = GasModalPageContainer.prototype + sinon.spy(GP, 'renderTabContent') + assert.equal(GP.renderTabContent.callCount, 0) + + wrapper.instance().renderTabs() + + assert.equal(GP.renderTabContent.callCount, 2) + + assert.deepEqual(GP.renderTabContent.firstCall.args, [wrapper.instance().renderBasicTabContent]) + assert.deepEqual(GP.renderTabContent.secondCall.args, [wrapper.instance().renderAdvancedTabContent]) + + GP.renderTabContent.restore() + }) + }) + + describe('renderTabContent', () => { + it('should render a root gas-modal-content div', () => { + const renderTabContentResult = wrapper.instance().renderTabContent(() => {}) + const renderedTabContent = shallow(renderTabContentResult) + assert.equal(renderedTabContent.props().className, 'gas-modal-content') + }) + + it('should render the element returned by the passed func as its first child', () => { + const renderTabContentResult = wrapper.instance().renderTabContent(() => Mock content) + const renderedTabContent = shallow(renderTabContentResult) + assert(renderedTabContent.childAt(0).equals(Mock content)) + }) + + it('should render the element results of renderInfoRow calls as second and third childs', () => { + const GP = GasModalPageContainer.prototype + sinon.stub(GP, 'renderInfoRow').callsFake((...args) => args.join(',')) + + const renderTabContentResult = wrapper.instance().renderTabContent(() => Mock content) + const renderedTabContent = shallow(renderTabContentResult) + assert.equal(renderedTabContent.childAt(1).text(), 'info-row--fade,originalTotal,$20.02 USD,0.06685 ETH') + assert.equal(renderedTabContent.childAt(2).text(), 'info-row,newTotal,$20.02 USD,0.06685 ETH') + + GP.renderInfoRow.restore() + }) + }) + + describe('renderInfoRow', () => { + it('should render a div with the passed className and two children, each with the expected text', () => { + const renderInfoRowResult = wrapper.instance().renderInfoRow('mockClassName', 'mockLabelKey', 'mockFiatAmount', 'mockCryptoAmount') + const renderedInfoRow = shallow(renderInfoRowResult) + assert.equal(renderedInfoRow.props().className, 'mockClassName') + + const firstChild = renderedInfoRow.childAt(0) + const secondhild = renderedInfoRow.childAt(1) + + assert.equal(firstChild.props().className, 'total-info') + assert.equal(secondhild.props().className, 'sum-info') + + assert.equal(firstChild.childAt(0).text(), 'mockLabelKey:') + assert.equal(firstChild.childAt(1).text(), 'mockFiatAmount') + assert.equal(secondhild.childAt(0).text(), 'amountPlusTxFee') + assert.equal(secondhild.childAt(1).text(), 'mockCryptoAmount') + }) + }) + + describe('renderBasicTabContent', () => { + it('should render', () => { + const renderBasicTabContentResult = wrapper.instance().renderBasicTabContent() + const renderedBasicTabContent = shallow(renderBasicTabContentResult) + assert.equal(renderedBasicTabContent.props().className, 'basic-tab') + }) + }) + + describe('renderAdvancedTabContent', () => { + it('should render', () => { + const renderAdvancedTabContentResult = wrapper.instance().renderAdvancedTabContent() + const renderedAdvancedTabContent = shallow(renderAdvancedTabContentResult) + assert.equal(renderedAdvancedTabContent.props().className, 'advanced-tab') + }) + }) +}) diff --git a/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js b/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js new file mode 100644 index 000000000..5b133fbe2 --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js @@ -0,0 +1,44 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' +import sinon from 'sinon' + +// let mapStateToProps +let mapDispatchToProps + +const actionSpies = { + hideModal: sinon.spy(), +} + +proxyquire('../gas-modal-page-container.container.js', { + 'react-redux': { + connect: (ms, md) => { + // mapStateToProps = ms + mapDispatchToProps = md + return () => ({}) + }, + }, + '../../../actions': actionSpies, +}) + +describe('gas-modal-page-container container', () => { + + describe('mapDispatchToProps()', () => { + let dispatchSpy + let mapDispatchToPropsObject + + beforeEach(() => { + dispatchSpy = sinon.spy() + mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy) + }) + + describe('hideModal()', () => { + it('should dispatch a hideModal action', () => { + mapDispatchToPropsObject.hideModal() + assert(dispatchSpy.calledOnce) + assert(actionSpies.hideModal.calledOnce) + }) + }) + + }) + +}) diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss index f901aed7d..48c6496d9 100644 --- a/ui/app/components/index.scss +++ b/ui/app/components/index.scss @@ -63,3 +63,5 @@ @import './sidebars/index'; @import './unit-input/index'; + +@import './gas-customization/gas-modal-page-container/index' diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js index 5aff4f5e1..46347312c 100644 --- a/ui/app/components/modals/modal.js +++ b/ui/app/components/modals/modal.js @@ -17,18 +17,17 @@ const ExportPrivateKeyModal = require('./export-private-key-modal') const NewAccountModal = require('./new-account-modal') const ShapeshiftDepositTxModal = require('./shapeshift-deposit-tx-modal.js') const HideTokenConfirmationModal = require('./hide-token-confirmation-modal') -const CustomizeGasModal = require('../customize-gas-modal') const NotifcationModal = require('./notification-modal') const QRScanner = require('./qr-scanner') import ConfirmRemoveAccount from './confirm-remove-account' import ConfirmResetAccount from './confirm-reset-account' import TransactionConfirmed from './transaction-confirmed' -import ConfirmCustomizeGasModal from './customize-gas' import CancelTransaction from './cancel-transaction' import WelcomeBeta from './welcome-beta' import RejectTransactions from './reject-transactions' import ClearApprovedOrigins from './clear-approved-origins' +import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container' const modalContainerBaseStyle = { transform: 'translate3d(-50%, 0, 0px)', @@ -295,7 +294,7 @@ const MODALS = { CUSTOMIZE_GAS: { contents: [ - h(CustomizeGasModal), + h(ConfirmCustomizeGasModal), ], mobileModalStyle: { width: '100vw', @@ -307,35 +306,16 @@ const MODALS = { margin: '0 auto', }, laptopModalStyle: { - width: '720px', - height: '377px', + width: 'auto', + height: '0px', top: '80px', + left: '0px', transform: 'none', - left: '0', - right: '0', margin: '0 auto', + position: 'relative', }, - }, - - CONFIRM_CUSTOMIZE_GAS: { - contents: h(ConfirmCustomizeGasModal), - mobileModalStyle: { - width: '100vw', - height: '100vh', - top: '0', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - }, - laptopModalStyle: { - width: '720px', - height: '377px', - top: '80px', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', + contentStyle: { + borderRadius: '8px', }, }, diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js index 626143ac7..a3ec82863 100644 --- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js +++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js @@ -117,7 +117,7 @@ const mapDispatchToProps = dispatch => { return dispatch(showModal({ name: 'TRANSACTION_CONFIRMED', onSubmit })) }, showCustomizeGasModal: ({ txData, onSubmit, validate }) => { - return dispatch(showModal({ name: 'CONFIRM_CUSTOMIZE_GAS', txData, onSubmit, validate })) + return dispatch(showModal({ name: 'CUSTOMIZE_GAS', txData, onSubmit, validate })) }, updateGasAndCalculate: ({ gasLimit, gasPrice }) => { return dispatch(updateGasAndCalculate({ gasLimit, gasPrice })) diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss index f90c8edc3..6c2b82f39 100644 --- a/ui/app/css/itcss/settings/variables.scss +++ b/ui/app/css/itcss/settings/variables.scss @@ -56,6 +56,7 @@ $zumthor: #edf7ff; $ecstasy: #f7861c; $linen: #fdf4f4; $oslo-gray: #8C8E94; +$polar: #fafcfe; /* Z-Indicies