diff options
author | Dan J Miller <danjm.com@gmail.com> | 2018-12-05 00:32:08 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-05 00:32:08 +0800 |
commit | d1996509deccd98d7ffa4cc360dc96c5392879d3 (patch) | |
tree | c8d9dbf1a3b393573f51af2563cdc3f589b1a162 /ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content | |
parent | 35670e926116b19e66931dace838d785adffac09 (diff) | |
parent | 8194309a9a7319bcebd6761a4596c208375adfab (diff) | |
download | tangerine-wallet-browser-d1996509deccd98d7ffa4cc360dc96c5392879d3.tar.gz tangerine-wallet-browser-d1996509deccd98d7ffa4cc360dc96c5392879d3.tar.zst tangerine-wallet-browser-d1996509deccd98d7ffa4cc360dc96c5392879d3.zip |
Merge pull request #5704 from MetaMask/new-gas-customize-feature-branch-d
Gas customization features
Diffstat (limited to 'ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content')
9 files changed, 701 insertions, 0 deletions
diff --git a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js new file mode 100644 index 000000000..ba738ff75 --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js @@ -0,0 +1,144 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' +import Loading from '../../../loading-screen' +import GasPriceChart from '../../gas-price-chart' +import debounce from 'lodash.debounce' + +export default class AdvancedTabContent extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + updateCustomGasPrice: PropTypes.func, + updateCustomGasLimit: PropTypes.func, + customGasPrice: PropTypes.number, + customGasLimit: PropTypes.number, + gasEstimatesLoading: PropTypes.bool, + millisecondsRemaining: PropTypes.number, + totalFee: PropTypes.string, + timeRemaining: PropTypes.string, + gasChartProps: PropTypes.object, + insufficientBalance: PropTypes.bool, + } + + constructor (props) { + super(props) + + this.debouncedGasLimitReset = debounce((dVal) => { + if (dVal < 21000) { + props.updateCustomGasLimit(21000) + } + }, 1000, { trailing: true }) + this.onChangeGasLimit = (val) => { + props.updateCustomGasLimit(val) + this.debouncedGasLimitReset(val) + } + } + + gasInput (value, onChange, min, insufficientBalance, showGWEI) { + return ( + <div className="advanced-tab__gas-edit-row__input-wrapper"> + <input + className={classnames('advanced-tab__gas-edit-row__input', { + 'advanced-tab__gas-edit-row__input--error': insufficientBalance, + })} + type="number" + value={value} + min={min} + onChange={event => onChange(Number(event.target.value))} + /> + <div className={classnames('advanced-tab__gas-edit-row__input-arrows', { + 'advanced-tab__gas-edit-row__input-arrows--error': insufficientBalance, + })}> + <div className="advanced-tab__gas-edit-row__input-arrows__i-wrap" onClick={() => onChange(value + 1)}><i className="fa fa-sm fa-angle-up" /></div> + <div className="advanced-tab__gas-edit-row__input-arrows__i-wrap" onClick={() => onChange(value - 1)}><i className="fa fa-sm fa-angle-down" /></div> + </div> + {insufficientBalance && <div className="advanced-tab__gas-edit-row__insufficient-balance"> + Insufficient Balance + </div>} + </div> + ) + } + + infoButton (onClick) { + return <i className="fa fa-info-circle" onClick={onClick} /> + } + + renderDataSummary (totalFee, timeRemaining) { + return ( + <div className="advanced-tab__transaction-data-summary"> + <div className="advanced-tab__transaction-data-summary__titles"> + <span>{ this.context.t('newTransactionFee') }</span> + <span>~{ this.context.t('transactionTime') }</span> + </div> + <div className="advanced-tab__transaction-data-summary__container"> + <div className="advanced-tab__transaction-data-summary__fee"> + {totalFee} + </div> + <div className="time-remaining">{timeRemaining}</div> + </div> + </div> + ) + } + + renderGasEditRow (labelKey, ...gasInputArgs) { + return ( + <div className="advanced-tab__gas-edit-row"> + <div className="advanced-tab__gas-edit-row__label"> + { this.context.t(labelKey) } + { this.infoButton(() => {}) } + </div> + { this.gasInput(...gasInputArgs) } + </div> + ) + } + + renderGasEditRows (customGasPrice, updateCustomGasPrice, customGasLimit, updateCustomGasLimit, insufficientBalance) { + return ( + <div className="advanced-tab__gas-edit-rows"> + { this.renderGasEditRow('gasPrice', customGasPrice, updateCustomGasPrice, customGasPrice, insufficientBalance, true) } + { this.renderGasEditRow('gasLimit', customGasLimit, this.onChangeGasLimit, customGasLimit, insufficientBalance) } + </div> + ) + } + + render () { + const { + updateCustomGasPrice, + updateCustomGasLimit, + timeRemaining, + customGasPrice, + customGasLimit, + insufficientBalance, + totalFee, + gasChartProps, + gasEstimatesLoading, + } = this.props + + return ( + <div className="advanced-tab"> + { this.renderDataSummary(totalFee, timeRemaining) } + <div className="advanced-tab__fee-chart"> + { this.renderGasEditRows( + customGasPrice, + updateCustomGasPrice, + customGasLimit, + updateCustomGasLimit, + insufficientBalance + ) } + <div className="advanced-tab__fee-chart__title">Live Gas Price Predictions</div> + {!gasEstimatesLoading + ? <GasPriceChart {...gasChartProps} updateCustomGasPrice={updateCustomGasPrice} /> + : <Loading /> + } + <div className="advanced-tab__fee-chart__speed-buttons"> + <span>Slower</span> + <span>Faster</span> + </div> + </div> + </div> + ) + } +} diff --git a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/index.js b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/index.js new file mode 100644 index 000000000..492037f25 --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/index.js @@ -0,0 +1 @@ +export { default } from './advanced-tab-content.component' diff --git a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss new file mode 100644 index 000000000..88c69faf4 --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss @@ -0,0 +1,191 @@ +@import './time-remaining/index'; + +.advanced-tab { + display: flex; + flex-flow: column; + + &__transaction-data-summary, + &__fee-chart-title { + padding-left: 24px; + padding-right: 24px; + } + + &__transaction-data-summary { + display: flex; + flex-flow: column; + color: $mid-gray; + margin-top: 12px; + padding-left: 18px; + padding-right: 18px; + + &__titles, + &__container { + display: flex; + flex-flow: row; + justify-content: space-between; + font-size: 12px; + color: #888EA3; + } + + &__container { + font-size: 16px; + margin-top: 0px; + } + + &__fee { + font-size: 16px; + color: #313A5E; + } + } + + &__fee-chart { + margin-top: 8px; + height: 265px; + background: #F8F9FB; + border-bottom: 1px solid #d2d8dd; + border-top: 1px solid #d2d8dd; + position: relative; + + &__title { + font-size: 12px; + color: #313A5E; + margin-left: 22px; + } + + &__speed-buttons { + position: absolute; + bottom: 13px; + display: flex; + justify-content: space-between; + padding-left: 20px; + padding-right: 19px; + width: 100%; + font-size: 10px; + color: #888EA3; + } + } + + &__slider-container { + padding-left: 27px; + padding-right: 27px; + } + + &__gas-edit-rows { + height: 73px; + display: flex; + flex-flow: row; + justify-content: space-between; + margin-left: 20px; + margin-right: 10px; + margin-top: 9px; + } + + &__gas-edit-row { + display: flex; + flex-flow: column; + + &__label { + color: #313B5E; + font-size: 14px; + display: flex; + justify-content: space-between; + align-items: center; + + .fa-info-circle { + color: $silver; + margin-left: 10px; + cursor: pointer; + } + + .fa-info-circle:hover { + color: $mid-gray; + } + } + + &__insufficient-balance { + font-size: 12px; + color: red; + } + + + &__input-wrapper { + position: relative; + } + + &__input { + border: 1px solid $dusty-gray; + border-radius: 4px; + color: $mid-gray; + font-size: 16px; + height: 24px; + width: 155px; + padding-left: 8px; + padding-top: 2px; + margin-top: 7px; + } + + &__input--error { + border: 1px solid $red; + } + + &__input-arrows { + position: absolute; + top: 7px; + right: 0px; + width: 17px; + height: 24px; + border: 1px solid #dadada; + border-top-right-radius: 4px; + display: flex; + flex-direction: column; + color: #9b9b9b; + font-size: .8em; + border-bottom-right-radius: 4px; + cursor: pointer; + + &__i-wrap { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + cursor: pointer; + } + + &__i-wrap:hover { + background: #4EADE7; + color: $white; + } + + i:hover { + background: #4EADE7; + } + + i { + font-size: 10px; + } + } + + &__input-arrows--error { + border: 1px solid $red; + } + + input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + -moz-appearance: none; + display: none; + } + + input[type="number"]:hover::-webkit-inner-spin-button { + -webkit-appearance: none; + -moz-appearance: none; + display: none; + } + + &__gwei-symbol { + position: absolute; + top: 8px; + right: 10px; + color: $dusty-gray; + } + } +}
\ No newline at end of file diff --git a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js new file mode 100644 index 000000000..d6920454d --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js @@ -0,0 +1,273 @@ +import React from 'react' +import assert from 'assert' +import shallow from '../../../../../../lib/shallow-with-context' +import sinon from 'sinon' +import AdvancedTabContent from '../advanced-tab-content.component.js' + +import GasPriceChart from '../../../gas-price-chart' +import Loading from '../../../../loading-screen' + +const propsMethodSpies = { + updateCustomGasPrice: sinon.spy(), + updateCustomGasLimit: sinon.spy(), +} + +sinon.spy(AdvancedTabContent.prototype, 'renderGasEditRow') +sinon.spy(AdvancedTabContent.prototype, 'gasInput') +sinon.spy(AdvancedTabContent.prototype, 'renderGasEditRows') +sinon.spy(AdvancedTabContent.prototype, 'renderDataSummary') + +describe('AdvancedTabContent Component', function () { + let wrapper + + beforeEach(() => { + wrapper = shallow(<AdvancedTabContent + updateCustomGasPrice={propsMethodSpies.updateCustomGasPrice} + updateCustomGasLimit={propsMethodSpies.updateCustomGasLimit} + customGasPrice={11} + customGasLimit={23456} + timeRemaining={21500} + totalFee={'$0.25'} + insufficientBalance={false} + />, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }) + }) + + afterEach(() => { + propsMethodSpies.updateCustomGasPrice.resetHistory() + propsMethodSpies.updateCustomGasLimit.resetHistory() + AdvancedTabContent.prototype.renderGasEditRow.resetHistory() + AdvancedTabContent.prototype.gasInput.resetHistory() + AdvancedTabContent.prototype.renderGasEditRows.resetHistory() + AdvancedTabContent.prototype.renderDataSummary.resetHistory() + }) + + describe('render()', () => { + it('should render the advanced-tab root node', () => { + assert(wrapper.hasClass('advanced-tab')) + }) + + it('should render the expected four children of the advanced-tab div', () => { + const advancedTabChildren = wrapper.children() + assert.equal(advancedTabChildren.length, 2) + + assert(advancedTabChildren.at(0).hasClass('advanced-tab__transaction-data-summary')) + assert(advancedTabChildren.at(1).hasClass('advanced-tab__fee-chart')) + + const feeChartDiv = advancedTabChildren.at(1) + + assert(feeChartDiv.childAt(0).hasClass('advanced-tab__gas-edit-rows')) + assert(feeChartDiv.childAt(1).hasClass('advanced-tab__fee-chart__title')) + assert(feeChartDiv.childAt(2).is(GasPriceChart)) + assert(feeChartDiv.childAt(3).hasClass('advanced-tab__fee-chart__speed-buttons')) + }) + + it('should render a loading component instead of the chart if gasEstimatesLoading is true', () => { + wrapper.setProps({ gasEstimatesLoading: true }) + const advancedTabChildren = wrapper.children() + assert.equal(advancedTabChildren.length, 2) + + assert(advancedTabChildren.at(0).hasClass('advanced-tab__transaction-data-summary')) + assert(advancedTabChildren.at(1).hasClass('advanced-tab__fee-chart')) + + const feeChartDiv = advancedTabChildren.at(1) + + assert(feeChartDiv.childAt(0).hasClass('advanced-tab__gas-edit-rows')) + assert(feeChartDiv.childAt(1).hasClass('advanced-tab__fee-chart__title')) + assert(feeChartDiv.childAt(2).is(Loading)) + assert(feeChartDiv.childAt(3).hasClass('advanced-tab__fee-chart__speed-buttons')) + }) + + it('should call renderDataSummary with the expected params', () => { + assert.equal(AdvancedTabContent.prototype.renderGasEditRows.callCount, 1) + const renderDataSummaryArgs = AdvancedTabContent.prototype.renderDataSummary.getCall(0).args + assert.deepEqual(renderDataSummaryArgs, ['$0.25', 21500]) + }) + + it('should call renderGasEditRows with the expected params', () => { + assert.equal(AdvancedTabContent.prototype.renderGasEditRows.callCount, 1) + const renderGasEditRowArgs = AdvancedTabContent.prototype.renderGasEditRows.getCall(0).args + assert.deepEqual(renderGasEditRowArgs, [ + 11, propsMethodSpies.updateCustomGasPrice, 23456, propsMethodSpies.updateCustomGasLimit, false, + ]) + }) + }) + + describe('renderDataSummary()', () => { + let dataSummary + + beforeEach(() => { + dataSummary = shallow(wrapper.instance().renderDataSummary('mockTotalFee', 'mockMsRemaining')) + }) + + it('should render the transaction-data-summary root node', () => { + assert(dataSummary.hasClass('advanced-tab__transaction-data-summary')) + }) + + it('should render titles of the data', () => { + const titlesNode = dataSummary.children().at(0) + assert(titlesNode.hasClass('advanced-tab__transaction-data-summary__titles')) + assert.equal(titlesNode.children().at(0).text(), 'newTransactionFee') + assert.equal(titlesNode.children().at(1).text(), '~transactionTime') + }) + + it('should render the data', () => { + const dataNode = dataSummary.children().at(1) + assert(dataNode.hasClass('advanced-tab__transaction-data-summary__container')) + assert.equal(dataNode.children().at(0).text(), 'mockTotalFee') + assert(dataNode.children().at(1).hasClass('time-remaining')) + assert.equal(dataNode.children().at(1).text(), 'mockMsRemaining') + }) + }) + + describe('renderGasEditRow()', () => { + let gasEditRow + + beforeEach(() => { + AdvancedTabContent.prototype.gasInput.resetHistory() + gasEditRow = shallow(wrapper.instance().renderGasEditRow( + 'mockLabelKey', 'argA', 'argB' + )) + }) + + it('should render the gas-edit-row root node', () => { + assert(gasEditRow.hasClass('advanced-tab__gas-edit-row')) + }) + + it('should render a label and an input', () => { + const gasEditRowChildren = gasEditRow.children() + assert.equal(gasEditRowChildren.length, 2) + assert(gasEditRowChildren.at(0).hasClass('advanced-tab__gas-edit-row__label')) + assert(gasEditRowChildren.at(1).hasClass('advanced-tab__gas-edit-row__input-wrapper')) + }) + + it('should render the label key and info button', () => { + const gasRowLabelChildren = gasEditRow.children().at(0).children() + assert.equal(gasRowLabelChildren.length, 2) + assert(gasRowLabelChildren.at(0), 'mockLabelKey') + assert(gasRowLabelChildren.at(1).hasClass('fa-info-circle')) + }) + + it('should call this.gasInput with the correct args', () => { + const gasInputSpyArgs = AdvancedTabContent.prototype.gasInput.args + assert.deepEqual(gasInputSpyArgs[0], [ 'argA', 'argB' ]) + }) + }) + + describe('renderGasEditRows()', () => { + let gasEditRows + let tempOnChangeGasLimit + + beforeEach(() => { + tempOnChangeGasLimit = wrapper.instance().onChangeGasLimit + wrapper.instance().onChangeGasLimit = () => 'mockOnChangeGasLimit' + AdvancedTabContent.prototype.renderGasEditRow.resetHistory() + gasEditRows = shallow(wrapper.instance().renderGasEditRows( + 'mockGasPrice', + () => 'mockUpdateCustomGasPriceReturn', + 'mockGasLimit', + () => 'mockUpdateCustomGasLimitReturn', + false + )) + }) + + afterEach(() => { + wrapper.instance().onChangeGasLimit = tempOnChangeGasLimit + }) + + it('should render the gas-edit-rows root node', () => { + assert(gasEditRows.hasClass('advanced-tab__gas-edit-rows')) + }) + + it('should render two rows', () => { + const gasEditRowsChildren = gasEditRows.children() + assert.equal(gasEditRowsChildren.length, 2) + assert(gasEditRowsChildren.at(0).hasClass('advanced-tab__gas-edit-row')) + assert(gasEditRowsChildren.at(1).hasClass('advanced-tab__gas-edit-row')) + }) + + it('should call this.renderGasEditRow twice, with the expected args', () => { + const renderGasEditRowSpyArgs = AdvancedTabContent.prototype.renderGasEditRow.args + assert.equal(renderGasEditRowSpyArgs.length, 2) + assert.deepEqual(renderGasEditRowSpyArgs[0].map(String), [ + 'gasPrice', 'mockGasPrice', () => 'mockUpdateCustomGasPriceReturn', 'mockGasPrice', false, true, + ].map(String)) + assert.deepEqual(renderGasEditRowSpyArgs[1].map(String), [ + 'gasLimit', 'mockGasLimit', () => 'mockOnChangeGasLimit', 'mockGasLimit', false, + ].map(String)) + }) + }) + + describe('infoButton()', () => { + let infoButton + + beforeEach(() => { + AdvancedTabContent.prototype.renderGasEditRow.resetHistory() + infoButton = shallow(wrapper.instance().infoButton(() => 'mockOnClickReturn')) + }) + + it('should render the i element', () => { + assert(infoButton.hasClass('fa-info-circle')) + }) + + it('should pass the onClick argument to the i tag onClick prop', () => { + assert(infoButton.props().onClick(), 'mockOnClickReturn') + }) + }) + + describe('gasInput()', () => { + let gasInput + + beforeEach(() => { + AdvancedTabContent.prototype.renderGasEditRow.resetHistory() + gasInput = shallow(wrapper.instance().gasInput( + 321, + value => value + 7, + 0, + false, + 8 + )) + }) + + it('should render the input-wrapper root node', () => { + assert(gasInput.hasClass('advanced-tab__gas-edit-row__input-wrapper')) + }) + + it('should render two children, including an input', () => { + assert.equal(gasInput.children().length, 2) + assert(gasInput.children().at(0).hasClass('advanced-tab__gas-edit-row__input')) + }) + + it('should pass the correct value min and precision props to the input', () => { + const inputProps = gasInput.find('input').props() + assert.equal(inputProps.min, 0) + assert.equal(inputProps.value, 321) + }) + + it('should call the passed onChange method with the value of the input onChange event', () => { + const inputOnChange = gasInput.find('input').props().onChange + assert.equal(inputOnChange({ target: { value: 8} }), 15) + }) + + it('should have two input arrows', () => { + const upArrow = gasInput.find('.fa-angle-up') + assert.equal(upArrow.length, 1) + const downArrow = gasInput.find('.fa-angle-down') + assert.equal(downArrow.length, 1) + }) + + it('should call onChange with the value incremented decremented when its onchange method is called', () => { + gasInput = shallow(wrapper.instance().gasInput( + 321, + value => value + 7, + 0, + 8, + false + )) + const upArrow = gasInput.find('.advanced-tab__gas-edit-row__input-arrows__i-wrap').at(0) + assert.equal(upArrow.props().onClick(), 329) + const downArrow = gasInput.find('.advanced-tab__gas-edit-row__input-arrows__i-wrap').at(1) + assert.equal(downArrow.props().onClick(), 327) + }) + }) + +}) diff --git a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.js b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.js new file mode 100644 index 000000000..61b681e1a --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.js @@ -0,0 +1 @@ +export { default } from './time-remaining.component' diff --git a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.scss b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.scss new file mode 100644 index 000000000..e2115af7f --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.scss @@ -0,0 +1,17 @@ +.time-remaining { + color: #313A5E; + font-size: 16px; + + .minutes-num, .seconds-num { + font-size: 16px; + } + + .seconds-num { + margin-left: 7px; + font-size: 16px; + } + + .minutes-label, .seconds-label { + font-size: 16px; + } +}
\ No newline at end of file diff --git a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/tests/time-remaining-component.test.js b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/tests/time-remaining-component.test.js new file mode 100644 index 000000000..d8490272f --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/tests/time-remaining-component.test.js @@ -0,0 +1,30 @@ +import React from 'react' +import assert from 'assert' +import shallow from '../../../../../../../lib/shallow-with-context' +import TimeRemaining from '../time-remaining.component.js' + +describe('TimeRemaining Component', function () { + let wrapper + + beforeEach(() => { + wrapper = shallow(<TimeRemaining + milliseconds={495000} + />) + }) + + describe('render()', () => { + it('should render the time-remaining root node', () => { + assert(wrapper.hasClass('time-remaining')) + }) + + it('should render minutes and seconds numbers and labels', () => { + const timeRemainingChildren = wrapper.children() + assert.equal(timeRemainingChildren.length, 4) + assert.equal(timeRemainingChildren.at(0).text(), 8) + assert.equal(timeRemainingChildren.at(1).text(), 'minutesShorthand') + assert.equal(timeRemainingChildren.at(2).text(), 15) + assert.equal(timeRemainingChildren.at(3).text(), 'secondsShorthand') + }) + }) + +}) diff --git a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.component.js b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.component.js new file mode 100644 index 000000000..826d41f9c --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.component.js @@ -0,0 +1,33 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { getTimeBreakdown } from './time-remaining.utils' + +export default class TimeRemaining extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + milliseconds: PropTypes.number, + } + + render () { + const { + milliseconds, + } = this.props + + const { + minutes, + seconds, + } = getTimeBreakdown(milliseconds) + + return ( + <div className="time-remaining"> + <span className="minutes-num">{minutes}</span> + <span className="minutes-label">{this.context.t('minutesShorthand')}</span> + <span className="seconds-num">{seconds}</span> + <span className="seconds-label">{this.context.t('secondsShorthand')}</span> + </div> + ) + } +} diff --git a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.utils.js b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.utils.js new file mode 100644 index 000000000..cf43e0acb --- /dev/null +++ b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.utils.js @@ -0,0 +1,11 @@ +function getTimeBreakdown (milliseconds) { + return { + hours: Math.floor(milliseconds / 3600000), + minutes: Math.floor((milliseconds % 3600000) / 60000), + seconds: Math.floor((milliseconds % 60000) / 1000), + } +} + +module.exports = { + getTimeBreakdown, +} |