aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/components
diff options
context:
space:
mode:
authorDan J Miller <danjm.com@gmail.com>2017-10-14 04:19:22 +0800
committerDaniel Tsui <szehungdanieltsui@gmail.com>2017-10-14 04:19:22 +0800
commit803eaaf968161f16aaf72d59b979dfbb7fb9b352 (patch)
treeadf8cbf5240e592ae0ede85be1181132612b2d8a /ui/app/components
parent81f62a7443d47461b5f9b20f442392562458c79a (diff)
downloadtangerine-wallet-browser-803eaaf968161f16aaf72d59b979dfbb7fb9b352.tar.gz
tangerine-wallet-browser-803eaaf968161f16aaf72d59b979dfbb7fb9b352.tar.zst
tangerine-wallet-browser-803eaaf968161f16aaf72d59b979dfbb7fb9b352.zip
[NewUI] SendV2-#8: Send container handles tokens; gas info dynamic from state (#2364)
* Adds memo field to send-v2. * Vertical align transaction with flexbox. * Customize Gas UI * Remove internal state from InputNumber and fix use in gastooltip. * Move customize-gas-modal to its own folder and minor cleanup * Create send container, get account info from state, and make currency display more reusable * Adjusts send-v2 and container for send-token. Dynamically getting suggested gas prices.
Diffstat (limited to 'ui/app/components')
-rw-r--r--ui/app/components/customize-gas-modal/gas-modal-card.js55
-rw-r--r--ui/app/components/customize-gas-modal/gas-slider.js50
-rw-r--r--ui/app/components/customize-gas-modal/index.js91
-rw-r--r--ui/app/components/input-number.js29
-rw-r--r--ui/app/components/modals/modal.js26
-rw-r--r--ui/app/components/send/account-list-item.js33
-rw-r--r--ui/app/components/send/currency-display.js61
-rw-r--r--ui/app/components/send/from-dropdown.js2
-rw-r--r--ui/app/components/send/gas-fee-display-v2.js47
-rw-r--r--ui/app/components/send/gas-tooltip.js4
-rw-r--r--ui/app/components/send/memo-textarea.js33
-rw-r--r--ui/app/components/send/send-v2-container.js62
-rw-r--r--ui/app/components/send/to-autocomplete.js4
13 files changed, 445 insertions, 52 deletions
diff --git a/ui/app/components/customize-gas-modal/gas-modal-card.js b/ui/app/components/customize-gas-modal/gas-modal-card.js
new file mode 100644
index 000000000..8e739ee40
--- /dev/null
+++ b/ui/app/components/customize-gas-modal/gas-modal-card.js
@@ -0,0 +1,55 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const InputNumber = require('../input-number.js')
+const GasSlider = require('./gas-slider.js')
+
+module.exports = GasModalCard
+
+inherits(GasModalCard, Component)
+function GasModalCard () {
+ Component.call(this)
+}
+
+GasModalCard.prototype.render = function () {
+ const {
+ memo,
+ identities,
+ onChange,
+ unitLabel,
+ value,
+ min,
+ max,
+ step,
+ title,
+ copy
+ } = this.props
+
+ return h('div.send-v2__gas-modal-card', [
+
+ h('div.send-v2__gas-modal-card__title', {}, title),
+
+ h('div.send-v2__gas-modal-card__copy', {}, copy),
+
+ h(InputNumber, {
+ unitLabel,
+ step,
+ max,
+ min,
+ placeholder: '0',
+ value,
+ onChange,
+ }),
+
+ h(GasSlider, {
+ value,
+ step,
+ max,
+ min,
+ onChange,
+ }),
+
+ ])
+
+}
+
diff --git a/ui/app/components/customize-gas-modal/gas-slider.js b/ui/app/components/customize-gas-modal/gas-slider.js
new file mode 100644
index 000000000..e76e96545
--- /dev/null
+++ b/ui/app/components/customize-gas-modal/gas-slider.js
@@ -0,0 +1,50 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+
+module.exports = GasSlider
+
+inherits(GasSlider, Component)
+function GasSlider () {
+ Component.call(this)
+}
+
+GasSlider.prototype.render = function () {
+ const {
+ memo,
+ identities,
+ onChange,
+ unitLabel,
+ value,
+ id,
+ step,
+ max,
+ min,
+ } = this.props
+
+ return h('div.gas-slider', [
+
+ h('input.gas-slider__input', {
+ type: 'range',
+ step,
+ max,
+ min,
+ value,
+ id: 'gasSlider',
+ onChange: event => onChange(event.target.value),
+ }, []),
+
+ h('div.gas-slider__bar', [
+
+ h('div.gas-slider__low'),
+
+ h('div.gas-slider__mid'),
+
+ h('div.gas-slider__high'),
+
+ ]),
+
+ ])
+
+}
+
diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js
new file mode 100644
index 000000000..91e2626b4
--- /dev/null
+++ b/ui/app/components/customize-gas-modal/index.js
@@ -0,0 +1,91 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const actions = require('../../actions')
+const GasModalCard = require('./gas-modal-card')
+
+function mapStateToProps (state) {
+ return {}
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ hideModal: () => dispatch(actions.hideModal()),
+ }
+}
+
+inherits(CustomizeGasModal, Component)
+function CustomizeGasModal () {
+ Component.call(this)
+
+ this.state = {
+ gasPrice: '0.23',
+ gasLimit: '25000',
+ }
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal)
+
+CustomizeGasModal.prototype.render = function () {
+ const { hideModal } = this.props
+ const { gasPrice, gasLimit } = this.state
+
+ return h('div.send-v2__customize-gas', {}, [
+ h('div', {
+ }, [
+ h('div.send-v2__customize-gas__header', {}, [
+
+ h('div.send-v2__customize-gas__title', 'Customize Gas'),
+
+ h('div.send-v2__customize-gas__close', {
+ onClick: hideModal,
+ }),
+
+ ]),
+
+ h('div.send-v2__customize-gas__body', {}, [
+
+ h(GasModalCard, {
+ value: gasPrice,
+ min: 0.0,
+ max: 5.0,
+ step: 0.01,
+ onChange: gasPrice => this.setState({ gasPrice }),
+ title: 'Gas Price',
+ copy: 'We calculate the suggested gas prices based on network success rates.',
+ }),
+
+ h(GasModalCard, {
+ value: gasLimit,
+ min: 20000,
+ max: 100000,
+ step: 1,
+ onChange: gasLimit => this.setState({ gasLimit }),
+ title: 'Gas Limit',
+ copy: 'We calculate the suggested gas limit based on network success rates.',
+ }),
+
+ ]),
+
+ h('div.send-v2__customize-gas__footer', {}, [
+
+ h('div.send-v2__customize-gas__revert', {
+ onClick: () => console.log('Revert'),
+ }, ['Revert']),
+
+ h('div.send-v2__customize-gas__buttons', [
+ h('div.send-v2__customize-gas__cancel', {
+ onClick: this.props.hideModal,
+ }, ['CANCEL']),
+
+ h('div.send-v2__customize-gas__save', {
+ onClick: () => console.log('Save'),
+ }, ['SAVE']),
+ ])
+
+ ]),
+
+ ]),
+ ])
+}
diff --git a/ui/app/components/input-number.js b/ui/app/components/input-number.js
index 2824d77aa..16347fd5e 100644
--- a/ui/app/components/input-number.js
+++ b/ui/app/components/input-number.js
@@ -1,6 +1,7 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
+const { addCurrencies } = require('../conversion-util')
module.exports = InputNumber
@@ -8,49 +9,37 @@ inherits(InputNumber, Component)
function InputNumber () {
Component.call(this)
- this.state = {
- value: 0,
- }
-
this.setValue = this.setValue.bind(this)
}
-InputNumber.prototype.componentWillMount = function () {
- const { initValue = 0 } = this.props
-
- this.setState({ value: initValue })
-}
-
InputNumber.prototype.setValue = function (newValue) {
- const { fixed, min = -1, onChange } = this.props
+ const { fixed, min = -1, max = Infinity, onChange } = this.props
- if (fixed) newValue = Number(newValue.toFixed(4))
+ newValue = Number(fixed ? newValue.toFixed(4) : newValue)
- if (newValue >= min) {
- this.setState({ value: newValue })
+ if (newValue >= min && newValue <= max) {
onChange(newValue)
}
}
InputNumber.prototype.render = function () {
- const { unitLabel, step = 1, placeholder } = this.props
- const { value } = this.state
+ const { unitLabel, step = 1, placeholder, value = 0 } = this.props
return h('div.customize-gas-input-wrapper', {}, [
h('input.customize-gas-input', {
placeholder,
type: 'number',
- value,
- onChange: (e) => this.setValue(Number(e.target.value)),
+ value: value,
+ onChange: (e) => this.setValue(e.target.value),
}),
h('span.gas-tooltip-input-detail', {}, [unitLabel]),
h('div.gas-tooltip-input-arrows', {}, [
h('i.fa.fa-angle-up', {
- onClick: () => this.setValue(value + step),
+ onClick: () => this.setValue(addCurrencies(value, step)),
}),
h('i.fa.fa-angle-down', {
style: { cursor: 'pointer' },
- onClick: () => this.setValue(value - step),
+ onClick: () => this.setValue(addCurrencies(value, step * -1)),
}),
]),
])
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
index 7247d840e..88deb2bb0 100644
--- a/ui/app/components/modals/modal.js
+++ b/ui/app/components/modals/modal.js
@@ -15,6 +15,7 @@ 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 accountModalStyle = {
mobileModalStyle: {
@@ -156,6 +157,31 @@ const MODALS = {
},
},
+ CUSTOMIZE_GAS: {
+ contents: [
+ h(CustomizeGasModal, {}, []),
+ ],
+ mobileModalStyle: {
+ width: '355px',
+ height: '598px',
+ // top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ top: '5%',
+ 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',
+ },
+ },
+
DEFAULT: {
contents: [],
mobileModalStyle: {},
diff --git a/ui/app/components/send/account-list-item.js b/ui/app/components/send/account-list-item.js
index b11527d95..64acde767 100644
--- a/ui/app/components/send/account-list-item.js
+++ b/ui/app/components/send/account-list-item.js
@@ -3,27 +3,34 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const Identicon = require('../identicon')
+const CurrencyDisplay = require('./currency-display')
+const { conversionRateSelector } = require('../../selectors')
inherits(AccountListItem, Component)
function AccountListItem () {
Component.call(this)
}
-module.exports = AccountListItem
+function mapStateToProps(state) {
+ return {
+ conversionRate: conversionRateSelector(state)
+ }
+}
+
+module.exports = connect(mapStateToProps)(AccountListItem)
AccountListItem.prototype.render = function () {
const {
account,
handleClick,
icon = null,
+ conversionRate,
} = this.props
- const { identity, balancesToRender } = account
- const { name, address } = identity
- const { primary, secondary } = balancesToRender
+ const { name, address, balance } = account
return h('div.account-list-item', {
- onClick: () => handleClick(identity),
+ onClick: () => handleClick({ name, address, balance }),
}, [
h('div.account-list-item__top-row', {}, [
@@ -35,7 +42,7 @@ AccountListItem.prototype.render = function () {
diameter: 18,
className: 'account-list-item__identicon',
},
- ),
+ ),
h('div.account-list-item__account-name', {}, name),
@@ -43,9 +50,17 @@ AccountListItem.prototype.render = function () {
]),
- h('div.account-list-item__account-primary-balance', {}, primary),
-
- h('div.account-list-item__account-secondary-balance', {}, secondary),
+ h(CurrencyDisplay, {
+ primaryCurrency: 'ETH',
+ convertedCurrency: 'USD',
+ value: balance,
+ conversionRate,
+ convertedPrefix: '$',
+ readOnly: true,
+ className: 'account-list-item__account-balances',
+ primaryBalanceClassName: 'account-list-item__account-primary-balance',
+ convertedBalanceClassName: 'account-list-item__account-secondary-balance',
+ }, name),
])
} \ No newline at end of file
diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js
index 332d722ec..ed9847fdb 100644
--- a/ui/app/components/send/currency-display.js
+++ b/ui/app/components/send/currency-display.js
@@ -11,8 +11,7 @@ function CurrencyDisplay () {
Component.call(this)
this.state = {
- minWidth: null,
- currentScrollWidth: null,
+ value: null,
}
}
@@ -29,28 +28,50 @@ function resetCaretIfPastEnd (value, event) {
}
}
+CurrencyDisplay.prototype.handleChangeInHexWei = function (value) {
+ const { handleChange } = this.props
+
+ const valueInHexWei = conversionUtil(value, {
+ fromNumericBase: 'dec',
+ toNumericBase: 'hex',
+ toDenomination: 'WEI',
+ })
+
+ handleChange(valueInHexWei)
+}
+
CurrencyDisplay.prototype.render = function () {
const {
- className,
+ className = 'currency-display',
+ primaryBalanceClassName = 'currency-display__input',
+ convertedBalanceClassName = 'currency-display__converted-value',
+ conversionRate,
primaryCurrency,
convertedCurrency,
- value = '',
- placeholder = '0',
- conversionRate,
convertedPrefix = '',
+ placeholder = '0',
readOnly = false,
- handleChange,
+ value: initValue,
} = this.props
- const { minWidth } = this.state
+ const { value } = this.state
+
+ const initValueToRender = conversionUtil(initValue, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ fromDenomination: 'WEI',
+ numberOfDecimals: 6,
+ conversionRate,
+ })
- const convertedValue = conversionUtil(value, {
+ const convertedValue = conversionUtil(value || initValueToRender, {
fromNumericBase: 'dec',
fromCurrency: primaryCurrency,
toCurrency: convertedCurrency,
+ numberOfDecimals: 2,
conversionRate,
})
- return h('div.currency-display', {
+ return h('div', {
className,
}, [
@@ -58,35 +79,39 @@ CurrencyDisplay.prototype.render = function () {
h('div.currency-display__input-wrapper', [
- h('input.currency-display__input', {
- value: `${value} ${primaryCurrency}`,
+ h('input', {
+ className: primaryBalanceClassName,
+ value: `${value || initValueToRender} ${primaryCurrency}`,
placeholder: `${0} ${primaryCurrency}`,
readOnly,
onChange: (event) => {
let newValue = event.target.value.split(' ')[0]
if (newValue === '') {
- handleChange('0')
+ this.setState({ value: '0' })
}
else if (newValue.match(/^0[1-9]$/)) {
- handleChange(newValue.match(/[1-9]/)[0])
+ this.setState({ value: newValue.match(/[1-9]/)[0] })
}
else if (newValue && !isValidInput(newValue)) {
event.preventDefault()
}
else {
- handleChange(newValue)
+ this.setState({ value: newValue })
}
},
- onKeyUp: event => resetCaretIfPastEnd(value, event),
- onClick: event => resetCaretIfPastEnd(value, event),
+ onBlur: event => this.handleChangeInHexWei(event.target.value.split(' ')[0]),
+ onKeyUp: event => resetCaretIfPastEnd(value || initValueToRender, event),
+ onClick: event => resetCaretIfPastEnd(value || initValueToRender, event),
}),
]),
]),
- h('div.currency-display__converted-value', {}, `${convertedPrefix}${convertedValue} ${convertedCurrency}`),
+ h('div', {
+ className: convertedBalanceClassName,
+ }, `${convertedPrefix}${convertedValue} ${convertedCurrency}`),
])
diff --git a/ui/app/components/send/from-dropdown.js b/ui/app/components/send/from-dropdown.js
index fb0a00cc2..e8e1d43f0 100644
--- a/ui/app/components/send/from-dropdown.js
+++ b/ui/app/components/send/from-dropdown.js
@@ -14,7 +14,7 @@ function FromDropdown () {
FromDropdown.prototype.getListItemIcon = function (currentAccount, selectedAccount) {
const listItemIcon = h(`i.fa.fa-check.fa-lg`, { style: { color: '#02c9b1' } })
- return currentAccount.identity.address === selectedAccount.identity.address
+ return currentAccount.address === selectedAccount.address
? listItemIcon
: null
}
diff --git a/ui/app/components/send/gas-fee-display-v2.js b/ui/app/components/send/gas-fee-display-v2.js
new file mode 100644
index 000000000..226ae93f8
--- /dev/null
+++ b/ui/app/components/send/gas-fee-display-v2.js
@@ -0,0 +1,47 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const CurrencyDisplay = require('./currency-display');
+
+const { multiplyCurrencies } = require('../../conversion-util')
+
+module.exports = GasFeeDisplay
+
+inherits(GasFeeDisplay, Component)
+function GasFeeDisplay () {
+ Component.call(this)
+}
+
+GasFeeDisplay.prototype.render = function () {
+ const {
+ conversionRate,
+ gasLimit,
+ gasPrice,
+ onClick,
+ } = this.props
+
+ const readyToRender = Boolean(gasLimit && gasPrice)
+
+ return h('div', [
+
+ readyToRender
+ ? h(CurrencyDisplay, {
+ primaryCurrency: 'ETH',
+ convertedCurrency: 'USD',
+ value: multiplyCurrencies(gasLimit, gasPrice, { toNumericBase: 'hex' }),
+ conversionRate,
+ convertedPrefix: '$',
+ readOnly: true,
+ })
+ : h('div.currency-display', 'Loading...')
+ ,
+
+ h('div.send-v2__sliders-icon-container', {
+ onClick,
+ }, [
+ h('i.fa.fa-sliders.send-v2__sliders-icon'),
+ ])
+
+ ])
+}
+
diff --git a/ui/app/components/send/gas-tooltip.js b/ui/app/components/send/gas-tooltip.js
index bef419e48..46aff3499 100644
--- a/ui/app/components/send/gas-tooltip.js
+++ b/ui/app/components/send/gas-tooltip.js
@@ -73,7 +73,7 @@ GasTooltip.prototype.render = function () {
step: 1,
min: 0,
placeholder: '0',
- initValue: gasPrice,
+ value: gasPrice,
onChange: (newPrice) => this.updateGasPrice(newPrice),
}),
h('div.gas-tooltip-input-label', {
@@ -89,7 +89,7 @@ GasTooltip.prototype.render = function () {
step: 1,
min: 0,
placeholder: '0',
- initValue: gasLimit,
+ value: gasLimit,
onChange: (newLimit) => this.updateGasLimit(newLimit),
}),
]),
diff --git a/ui/app/components/send/memo-textarea.js b/ui/app/components/send/memo-textarea.js
new file mode 100644
index 000000000..4005b9493
--- /dev/null
+++ b/ui/app/components/send/memo-textarea.js
@@ -0,0 +1,33 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const Identicon = require('../identicon')
+
+module.exports = MemoTextArea
+
+inherits(MemoTextArea, Component)
+function MemoTextArea () {
+ Component.call(this)
+}
+
+MemoTextArea.prototype.render = function () {
+ const { memo, identities, onChange } = this.props
+
+ return h('div.send-v2__memo-text-area', [
+
+ h('textarea.send-v2__memo-text-area__input', {
+ placeholder: 'Optional',
+ value: memo,
+ onChange,
+ // onBlur: () => {
+ // this.setErrorsFor('memo')
+ // },
+ onFocus: event => {
+ // this.clearErrorsFor('memo')
+ },
+ }),
+
+ ])
+
+}
+
diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js
new file mode 100644
index 000000000..0c8dd5335
--- /dev/null
+++ b/ui/app/components/send/send-v2-container.js
@@ -0,0 +1,62 @@
+const connect = require('react-redux').connect
+const actions = require('../../actions')
+const abi = require('ethereumjs-abi')
+const SendEther = require('../../send-v2')
+
+const { multiplyCurrencies } = require('../../conversion-util')
+
+const {
+ accountsWithSendEtherInfoSelector,
+ getCurrentAccountWithSendEtherInfo,
+ conversionRateSelector,
+ getSelectedToken,
+ getSelectedTokenExchangeRate,
+ getSelectedAddress,
+} = require('../../selectors')
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(SendEther)
+
+function mapStateToProps (state) {
+ const selectedAddress = getSelectedAddress(state);
+ const selectedToken = getSelectedToken(state);
+ const tokenExchangeRates = state.metamask.tokenExchangeRates
+ const selectedTokenExchangeRate = getSelectedTokenExchangeRate(state)
+ const conversionRate = conversionRateSelector(state)
+
+ let data;
+ let primaryCurrency;
+ let tokenToUSDRate;
+ if (selectedToken) {
+ data = Array.prototype.map.call(
+ abi.rawEncode(['address', 'uint256'], [selectedAddress, '0x0']),
+ x => ('00' + x.toString(16)).slice(-2)
+ ).join('')
+
+ primaryCurrency = selectedToken.symbol
+
+ tokenToUSDRate = multiplyCurrencies(
+ conversionRate,
+ selectedTokenExchangeRate,
+ { toNumericBase: 'dec' }
+ )
+ }
+
+ return {
+ selectedAccount: getCurrentAccountWithSendEtherInfo(state),
+ accounts: accountsWithSendEtherInfoSelector(state),
+ conversionRate,
+ selectedToken,
+ primaryCurrency,
+ data,
+ tokenToUSDRate,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ showCustomizeGasModal: () => dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })),
+ estimateGas: params => dispatch(actions.estimateGas(params)),
+ getGasPrice: () => dispatch(actions.getGasPrice()),
+ updateTokenExchangeRate: token => dispatch(actions.updateTokenExchangeRate(token)),
+ }
+}
diff --git a/ui/app/components/send/to-autocomplete.js b/ui/app/components/send/to-autocomplete.js
index 3808bf496..1bf1e1907 100644
--- a/ui/app/components/send/to-autocomplete.js
+++ b/ui/app/components/send/to-autocomplete.js
@@ -11,7 +11,7 @@ function ToAutoComplete () {
}
ToAutoComplete.prototype.render = function () {
- const { to, identities, onChange } = this.props
+ const { to, accounts, onChange } = this.props
return h('div.send-v2__to-autocomplete', [
@@ -32,7 +32,7 @@ ToAutoComplete.prototype.render = function () {
h('datalist#addresses', [
// Corresponds to the addresses owned.
- ...Object.entries(identities).map(([key, { address, name }]) => {
+ ...Object.entries(accounts).map(([key, { address, name }]) => {
return h('option', {
value: address,
label: name,