diff --git a/app/actions/modals/index.js b/app/actions/modals/index.js index 4c1f43b5ac3..0bda0082634 100644 --- a/app/actions/modals/index.js +++ b/app/actions/modals/index.js @@ -23,3 +23,9 @@ export function toggleReceiveModal(asset) { asset }; } + +export function toggleDappTransactionModal() { + return { + type: 'TOGGLE_DAPP_TRANSACTION_MODAL' + }; +} diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index 8698ddc523e..8b68c2ac1ce 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -102,6 +102,7 @@ import { hideTransactionNotification, showSimpleNotification } from '../../../actions/notification'; +import { toggleDappTransactionModal } from '../../../actions/modals'; import AccountApproval from '../../UI/AccountApproval'; const styles = StyleSheet.create({ @@ -439,9 +440,16 @@ class Main extends PureComponent { /** * Indicates whether third party API mode is enabled */ - thirdPartyApiMode: PropTypes.bool + thirdPartyApiMode: PropTypes.bool, + /** + /* Hides or shows dApp transaction modal + */ + toggleDappTransactionModal: PropTypes.func, + /** + /* dApp transaction modal visible or not + */ + dappTransactionModalVisible: PropTypes.bool }; - state = { connected: true, forceReload: false, @@ -816,7 +824,7 @@ class Main extends PureComponent { if (data && data.substr(0, 10) === APPROVE_FUNCTION_SIGNATURE) { this.props.navigation.push('ApproveView'); } else { - this.props.navigation.push('ApprovalView'); + this.props.toggleDappTransactionModal(); } } }; @@ -1050,6 +1058,12 @@ class Main extends PureComponent { backupAlertPress = () => { this.props.navigation.navigate('AccountBackupStep1'); }; + renderDappTransactionModal = () => ( + + ); render() { const { isPaymentChannelTransaction, isPaymentRequest } = this.props; @@ -1072,6 +1086,7 @@ class Main extends PureComponent { {this.renderSigningModal()} {this.renderWalletConnectSessionRequestModal()} + {this.props.dappTransactionModalVisible && this.renderDappTransactionModal()} ); } @@ -1087,7 +1102,8 @@ const mapStateToProps = state => ({ providerType: state.engine.backgroundState.NetworkController.provider.type, isPaymentChannelTransaction: state.transaction.paymentChannelTransaction, isPaymentRequest: state.transaction.paymentRequest, - identities: state.engine.backgroundState.PreferencesController.identities + identities: state.engine.backgroundState.PreferencesController.identities, + dappTransactionModalVisible: state.modals.dappTransactionModalVisible }); const mapDispatchToProps = dispatch => ({ @@ -1095,7 +1111,8 @@ const mapDispatchToProps = dispatch => ({ setTransactionObject: transaction => dispatch(setTransactionObject(transaction)), showTransactionNotification: args => dispatch(showTransactionNotification(args)), showSimpleNotification: args => dispatch(showSimpleNotification(args)), - hideTransactionNotification: () => dispatch(hideTransactionNotification()) + hideTransactionNotification: () => dispatch(hideTransactionNotification()), + toggleDappTransactionModal: () => dispatch(toggleDappTransactionModal()) }); export default connect( diff --git a/app/components/UI/CustomGas/__snapshots__/index.test.js.snap b/app/components/UI/CustomGas/__snapshots__/index.test.js.snap index 42bb83fa9f9..2763d6cb592 100644 --- a/app/components/UI/CustomGas/__snapshots__/index.test.js.snap +++ b/app/components/UI/CustomGas/__snapshots__/index.test.js.snap @@ -4,12 +4,18 @@ exports[`CustomGas should render correctly 1`] = ` - - Loading... - + `; diff --git a/app/components/UI/CustomGas/index.js b/app/components/UI/CustomGas/index.js index 71d3ba81bd8..cf3d6042187 100644 --- a/app/components/UI/CustomGas/index.js +++ b/app/components/UI/CustomGas/index.js @@ -1,8 +1,8 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { StyleSheet, View, Text, TextInput, TouchableOpacity } from 'react-native'; -import { colors, fontStyles, baseStyles } from '../../../styles/common'; +import { StyleSheet, View, Text, TextInput, TouchableOpacity, ActivityIndicator } from 'react-native'; +import { colors, fontStyles } from '../../../styles/common'; import { strings } from '../../../../locales/i18n'; import { getRenderableEthGasFee, @@ -10,35 +10,99 @@ import { apiEstimateModifiedToWEI, getBasicGasEstimates } from '../../../util/custom-gas'; +import IonicIcon from 'react-native-vector-icons/Ionicons'; import { BN } from 'ethereumjs-util'; -import { fromWei, renderWei, hexToBN } from '../../../util/number'; -import { getTicker } from '../../../util/transactions'; +import { fromWei, renderWei, hexToBN, isDecimal, isBN } from '../../../util/number'; +import { getTicker, getNormalizedTxState } from '../../../util/transactions'; +import { safeToChecksumAddress } from '../../../util/address'; import Radio from '../Radio'; +import StyledButton from '../../UI/StyledButton'; const styles = StyleSheet.create({ - labelText: { + root: { + paddingHorizontal: 24 + }, + customGasHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', + paddingBottom: 20 + }, + customGasModalTitleText: { ...fontStyles.bold, - color: colors.grey400, - fontSize: 16 + color: colors.black, + fontSize: 14, + alignSelf: 'center' + }, + optionsContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + paddingBottom: 20 + }, + basicButton: { + width: 116, + height: 36, + padding: 8, + justifyContent: 'center', + alignItems: 'center' + }, + optionSelected: { + backgroundColor: colors.grey000, + borderWidth: 1, + borderRadius: 20, + borderColor: colors.grey100 + }, + textOptions: { + ...fontStyles.normal, + fontSize: 14, + color: colors.black + }, + message: { + ...fontStyles.normal, + color: colors.black, + fontSize: 12, + paddingBottom: 20 + }, + warningWrapper: { + marginBottom: 20, + height: 50, + alignItems: 'center', + justifyContent: 'center' + }, + warningTextWrapper: { + width: '100%', + paddingHorizontal: 10, + paddingVertical: 8, + backgroundColor: colors.red000, + borderColor: colors.red, + borderRadius: 8, + borderWidth: 1, + justifyContent: 'center', + alignItems: 'center' + }, + warningText: { + color: colors.red, + fontSize: 12, + ...fontStyles.normal + }, + invisible: { + opacity: 0 }, titleContainer: { - flex: 1, width: '100%', flexDirection: 'row', justifyContent: 'space-between' }, - titleMargin: { - marginBottom: 10, - alignItems: 'flex-end' - }, radio: { marginLeft: 'auto' }, selectors: { - flex: 1, position: 'relative', flexDirection: 'row', - justifyContent: 'space-evenly' + justifyContent: 'space-evenly', + marginBottom: 8 }, selector: { alignSelf: 'stretch', @@ -56,10 +120,6 @@ const styles = StyleSheet.create({ borderColor: colors.blue, zIndex: 1 }, - advancedOptions: { - textAlign: 'right', - alignItems: 'flex-end' - }, slow: { borderBottomStartRadius: 6, borderTopStartRadius: 6 @@ -78,9 +138,6 @@ const styles = StyleSheet.create({ fontSize: 10, color: colors.black }, - textTotalGas: { - ...fontStyles.bold - }, textTime: { ...fontStyles.bold, color: colors.black, @@ -88,27 +145,53 @@ const styles = StyleSheet.create({ fontSize: 18, textTransform: 'none' }, - textAdvancedOptions: { - color: colors.blue, + loaderContainer: { + height: 200, + backgroundColor: colors.white, + alignItems: 'center', + justifyContent: 'center' + }, + advancedOptionsContainer: { + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center' + }, + valueRow: { + width: '100%', + flexDirection: 'row', + alignItems: 'center', + marginBottom: 20 + }, + advancedOptionsText: { + flex: 1, + textAlign: 'left', + ...fontStyles.light, + color: colors.black, + fontSize: 16 + }, + totalGasWrapper: { + flex: 1, + flexDirection: 'row', + justifyContent: 'flex-start', + paddingVertical: 8, + paddingRight: 20 + }, + textTotalGas: { + ...fontStyles.bold, + color: colors.black, fontSize: 14 }, gasInput: { + flex: 1, ...fontStyles.bold, backgroundColor: colors.white, borderColor: colors.grey100, - borderRadius: 4, + borderRadius: 8, borderWidth: 1, - fontSize: 16, - paddingBottom: 8, - paddingLeft: 10, - paddingRight: 52, - paddingTop: 8, - position: 'relative', - marginTop: 5 - }, - warningText: { - color: colors.red, - ...fontStyles.normal + fontSize: 14, + paddingHorizontal: 10, + paddingVertical: 8, + position: 'relative' } }); @@ -117,6 +200,10 @@ const styles = StyleSheet.create({ */ class CustomGas extends PureComponent { static propTypes = { + /** + * List of accounts from the AccountTrackerController + */ + accounts: PropTypes.object, /** /* conversion rate of ETH - FIAT */ @@ -129,14 +216,14 @@ class CustomGas extends PureComponent { * Callback triggered when gas fee is selected */ handleGasFeeSelection: PropTypes.func, - /** - * Object BN containing total gas fee - */ - totalGas: PropTypes.object, /** * Object BN containing estimated gas limit */ gas: PropTypes.object, + /** + * Object BN containing gas price + */ + gasPrice: PropTypes.object, /** * Callback to modify state in parent state */ @@ -148,7 +235,15 @@ class CustomGas extends PureComponent { /** * Displayed when there is a gas station error */ - gasError: PropTypes.string + gasError: PropTypes.string, + /** + * Changes the mode to 'review' + */ + review: PropTypes.func, + /** + * Transaction object associated with this transaction + */ + transaction: PropTypes.object }; state = { @@ -167,13 +262,16 @@ class CustomGas extends PureComponent { advancedCustomGas: false, customGasPrice: '10', customGasLimit: fromWei(this.props.gas, 'wei'), + customGasPriceBN: this.props.gasPrice, + customGasLimitBN: this.props.gas, warningGasLimit: '', - warningGasPrice: '' + warningGasPrice: '', + warningSufficientFunds: '' }; onPressGasFast = () => { const { fastGwei } = this.state; - const { gas, onPress } = this.props; + const { onPress } = this.props; onPress && onPress(); this.setState({ gasFastSelected: true, @@ -182,12 +280,11 @@ class CustomGas extends PureComponent { selected: 'fast', customGasPrice: fastGwei }); - this.props.handleGasFeeSelection(gas, apiEstimateModifiedToWEI(fastGwei)); }; onPressGasAverage = () => { const { averageGwei } = this.state; - const { gas, onPress } = this.props; + const { onPress } = this.props; onPress && onPress(); this.setState({ gasFastSelected: false, @@ -196,12 +293,11 @@ class CustomGas extends PureComponent { selected: 'average', customGasPrice: averageGwei }); - this.props.handleGasFeeSelection(gas, apiEstimateModifiedToWEI(averageGwei)); }; onPressGasSlow = () => { const { safeLowGwei } = this.state; - const { gas, onPress } = this.props; + const { onPress } = this.props; onPress && onPress(); this.setState({ gasFastSelected: false, @@ -210,38 +306,26 @@ class CustomGas extends PureComponent { selected: 'slow', customGasPrice: safeLowGwei }); - this.props.handleGasFeeSelection(gas, apiEstimateModifiedToWEI(safeLowGwei)); }; - onAdvancedOptions = () => { - const { advancedCustomGas, selected, fastGwei, averageGwei, safeLowGwei, customGasPrice } = this.state; - const { gas, onPress } = this.props; - onPress && onPress(); - if (advancedCustomGas) { - switch (selected) { - case 'slow': - this.props.handleGasFeeSelection(gas, apiEstimateModifiedToWEI(safeLowGwei)); - break; - case 'average': - this.props.handleGasFeeSelection(gas, apiEstimateModifiedToWEI(averageGwei)); - break; - case 'fast': - this.props.handleGasFeeSelection(gas, apiEstimateModifiedToWEI(fastGwei)); - break; - } - } else { - this.setState({ customGasLimit: fromWei(gas, 'wei') }); + toggleAdvancedOptions = () => { + const { advancedCustomGas, customGasPrice } = this.state; + const { gas } = this.props; + if (!advancedCustomGas) { + this.setState({ customGasLimit: fromWei(gas, 'wei'), advancedCustomGas: !advancedCustomGas }); this.props.handleGasFeeSelection(gas, apiEstimateModifiedToWEI(customGasPrice)); + } else { + this.setState({ advancedCustomGas: !advancedCustomGas }); } - this.setState({ advancedCustomGas: !advancedCustomGas }); }; componentDidMount = async () => { + const { gas, gasPrice } = this.props; await this.handleFetchBasicEstimates(); + const warningSufficientFunds = this.hasSufficientFunds(gas, gasPrice); const { ticker } = this.props; - if (ticker && ticker !== 'ETH') { - this.setState({ advancedCustomGas: true }); - } + //Applies ISF error if present before any gas modifications + this.setState({ warningSufficientFunds, advancedCustomGas: ticker && ticker !== 'ETH' }); }; componentDidUpdate = prevProps => { @@ -252,9 +336,8 @@ class CustomGas extends PureComponent { handleGasRecalculationForCustomGasInput = prevProps => { const actualGasLimitWei = renderWei(hexToBN(this.props.gas)); - if (renderWei(hexToBN(prevProps.gas)) !== actualGasLimitWei) { + if (renderWei(hexToBN(prevProps.gas)) !== actualGasLimitWei) this.setState({ customGasLimit: actualGasLimitWei }); - } }; handleFetchBasicEstimates = async () => { @@ -263,17 +346,73 @@ class CustomGas extends PureComponent { this.setState({ ...basicGasEstimates, ready: true }); }; + //Validate locally instead of in TransactionEditor, otherwise cannot change back to review mode if insufficient funds + hasSufficientFunds = (gas, gasPrice) => { + const { + transaction: { from, value } + } = this.props; + const checksummedFrom = safeToChecksumAddress(from) || ''; + const fromAccount = this.props.accounts[checksummedFrom]; + if (hexToBN(fromAccount.balance).lt(gas.mul(gasPrice).add(value))) return strings('transaction.insufficient'); + return ''; + }; + onGasLimitChange = value => { - const { customGasPrice } = this.state; + const { customGasPriceBN } = this.state; const bnValue = new BN(value); - this.setState({ customGasLimit: value }); - this.props.handleGasFeeSelection(bnValue, apiEstimateModifiedToWEI(customGasPrice)); + const warningSufficientFunds = this.hasSufficientFunds(bnValue, customGasPriceBN); + let warningGasLimit; + if (!value || value === '' || !isDecimal(value)) warningGasLimit = strings('transaction.invalid_gas'); + else if (bnValue && !isBN(bnValue)) warningGasLimit = strings('transaction.invalid_gas'); + else if (bnValue.lt(new BN(21000)) || bnValue.gt(new BN(7920028))) + warningGasLimit = strings('custom_gas.warning_gas_limit'); + + this.setState({ + customGasLimit: value, + customGasLimitBN: bnValue, + warningGasLimit, + warningSufficientFunds + }); }; onGasPriceChange = value => { - const { customGasLimit } = this.state; - this.setState({ customGasPrice: value }); - this.props.handleGasFeeSelection(new BN(customGasLimit, 10), apiEstimateModifiedToWEI(value)); + const { customGasLimitBN } = this.state; + //Added because apiEstimateModifiedToWEI doesn't like empty strings + const gasPrice = value === '' ? '0' : value; + const gasPriceBN = apiEstimateModifiedToWEI(gasPrice); + const warningSufficientFunds = this.hasSufficientFunds(customGasLimitBN, gasPriceBN); + let warningGasPrice; + if (!value || value === '' || !isDecimal(value) || value <= 0) + warningGasPrice = strings('transaction.invalid_gas_price'); + if (gasPriceBN && !isBN(gasPriceBN)) warningGasPrice = strings('transaction.invalid_gas_price'); + this.setState({ + customGasPrice: value, + customGasPriceBN: gasPriceBN, + warningGasPrice, + warningSufficientFunds + }); + }; + + //Handle gas fee selection when save button is pressed instead of everytime a change is made, otherwise cannot switch back to review mode if there is an error + saveCustomGasSelection = () => { + const { + selected, + fastGwei, + averageGwei, + safeLowGwei, + customGasLimit, + customGasPrice, + advancedCustomGas + } = this.state; + const { review, gas, handleGasFeeSelection } = this.props; + if (advancedCustomGas) { + handleGasFeeSelection(new BN(customGasLimit), apiEstimateModifiedToWEI(customGasPrice)); + } else { + if (selected === 'slow') handleGasFeeSelection(gas, apiEstimateModifiedToWEI(safeLowGwei)); + if (selected === 'average') handleGasFeeSelection(gas, apiEstimateModifiedToWEI(averageGwei)); + if (selected === 'fast') handleGasFeeSelection(gas, apiEstimateModifiedToWEI(fastGwei)); + } + review(); }; renderCustomGasSelector = () => { @@ -354,71 +493,119 @@ class CustomGas extends PureComponent { }; renderCustomGasInput = () => { - const { customGasLimit, customGasPrice, warningGasLimit, warningGasPrice } = this.state; - const { totalGas } = this.props; + const { customGasPrice, customGasLimitBN, customGasPriceBN } = this.state; + const totalGas = customGasLimitBN.mul(customGasPriceBN); const ticker = getTicker(this.props.ticker); return ( - - - {fromWei(totalGas)} {ticker} - - {strings('custom_gas.gas_limit')} - - {warningGasLimit} - {strings('custom_gas.gas_price')} - - {warningGasPrice} + + + {strings('custom_gas.total')} + + + {fromWei(totalGas)} {ticker} + + + + + {strings('custom_gas.gas_limit')} + + + + {strings('custom_gas.gas_price')} + + + + ); + }; + + renderGasError = () => { + const { warningGasLimit, warningGasPrice, warningSufficientFunds } = this.state; + const { gasError } = this.props; + const gasErrorMessage = warningGasPrice || warningGasLimit || warningSufficientFunds || gasError; + return ( + + + {gasErrorMessage} + ); }; render = () => { if (this.state.ready) { - const { advancedCustomGas } = this.state; - const { gasError } = this.props; + const { advancedCustomGas, warningGasLimit, warningGasPrice, warningSufficientFunds } = this.state; + const { review, gasError } = this.props; + const disableButton = advancedCustomGas + ? !!warningGasLimit || !!warningGasPrice || !!warningSufficientFunds || !!gasError + : false; return ( - - - - {strings('transaction.gas_fee')}: - {gasError ? {gasError} : null} - - - - - {advancedCustomGas - ? strings('custom_gas.hide_advanced_options') - : strings('custom_gas.advanced_options')} - - - + + + + + + {strings('transaction.edit_network_fee')} + + + + {strings('custom_gas.basic_options')} + + + {strings('custom_gas.advanced_options')} + + + {advancedCustomGas ? this.renderCustomGasInput() : this.renderCustomGasSelector()} + {!advancedCustomGas ? ( + {strings('custom_gas.cost_explanation')} + ) : null} + {advancedCustomGas ? this.renderGasError() : null} + + + {strings('custom_gas.save')} + + ); } return ( - - {strings('transaction.loading')} + + ); }; } const mapStateToProps = state => ({ + accounts: state.engine.backgroundState.AccountTrackerController.accounts, conversionRate: state.engine.backgroundState.CurrencyRateController.conversionRate, currentCurrency: state.engine.backgroundState.CurrencyRateController.currentCurrency, - ticker: state.engine.backgroundState.NetworkController.provider.ticker + ticker: state.engine.backgroundState.NetworkController.provider.ticker, + transaction: getNormalizedTxState(state) }); export default connect(mapStateToProps)(CustomGas); diff --git a/app/components/UI/CustomGas/index.test.js b/app/components/UI/CustomGas/index.test.js index 32615ed9f5b..682acdef87f 100644 --- a/app/components/UI/CustomGas/index.test.js +++ b/app/components/UI/CustomGas/index.test.js @@ -17,12 +17,21 @@ describe('CustomGas', () => { currentCurrency: 'usd', conversionRate: 0.1 }, + AccountTrackerController: { + accounts: { + '0x': '0x' + } + }, NetworkController: { provider: { ticker: 'ETH' } } } + }, + transaction: { + from: '0x', + value: 100 } }; diff --git a/app/components/UI/TransactionEdit/__snapshots__/index.test.js.snap b/app/components/UI/TransactionEdit/__snapshots__/index.test.js.snap index 27053dee41c..92839aca650 100644 --- a/app/components/UI/TransactionEdit/__snapshots__/index.test.js.snap +++ b/app/components/UI/TransactionEdit/__snapshots__/index.test.js.snap @@ -1,297 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`TransactionEdit should render correctly 1`] = ` - - - - - - - From - : - - - - - - - - Amount - : - - - - Max - - - - - - - - - To - : - - - - - - - - - - - Hex Data - : - - - - - - - + `; diff --git a/app/components/UI/TransactionEdit/index.js b/app/components/UI/TransactionEdit/index.js index bba68f392f2..5360ff9f979 100644 --- a/app/components/UI/TransactionEdit/index.js +++ b/app/components/UI/TransactionEdit/index.js @@ -1,135 +1,16 @@ import React, { PureComponent } from 'react'; -import AccountInput from '../AccountInput'; -import AccountSelect from '../AccountSelect'; -import ActionView from '../ActionView'; -import EthInput from '../EthInput'; import PropTypes from 'prop-types'; -import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'; -import { colors, fontStyles } from '../../../styles/common'; import { connect } from 'react-redux'; -import { toBN, isBN, hexToBN, fromWei, fromTokenMinimalUnit, toTokenMinimalUnit } from '../../../util/number'; -import { strings } from '../../../../locales/i18n'; +import { toBN, isBN, fromWei } from '../../../util/number'; import CustomGas from '../CustomGas'; import { addHexPrefix } from 'ethereumjs-util'; -import { getTransactionOptionsTitle } from '../../UI/Navbar'; -import PaymentChannelsClient from '../../../core/PaymentChannelsClient'; -import Device from '../../../util/Device'; import { getNormalizedTxState } from '../../../util/transactions'; -const styles = StyleSheet.create({ - root: { - backgroundColor: colors.white, - flex: 1 - }, - formRow: { - flexDirection: 'row' - }, - fromRow: { - marginRight: 0, - position: 'absolute', - zIndex: 5, - right: 24, - left: 24, - marginTop: 30 - }, - toRow: { - right: 24, - left: 24, - marginTop: Device.isAndroid() ? 125 : 120, - position: 'absolute', - zIndex: 4 - }, - row: { - marginTop: 18, - zIndex: 3 - }, - amountRow: { - right: 24, - left: 24, - marginTop: Device.isAndroid() ? 200 : 190, - position: 'absolute', - zIndex: 4 - }, - notAbsolute: { - marginTop: Device.isAndroid() ? 270 : 240 - }, - label: { - flex: 0, - paddingRight: 18, - width: 106 - }, - labelText: { - ...fontStyles.bold, - color: colors.grey400, - fontSize: 16 - }, - max: { - ...fontStyles.bold, - color: colors.blue, - fontSize: 12, - paddingTop: 6 - }, - error: { - ...fontStyles.bold, - color: colors.red, - fontSize: 12, - lineHeight: 12, - paddingTop: 6 - }, - warning: { - ...fontStyles.bold, - color: colors.orange300, - fontSize: 12, - lineHeight: 12, - paddingTop: 6 - }, - form: { - flex: 1, - paddingVertical: 16, - paddingHorizontal: 24, - flexDirection: 'column' - }, - androidForm: { - paddingBottom: 100, - minHeight: 500 - }, - hexData: { - ...fontStyles.bold, - backgroundColor: colors.white, - borderColor: colors.grey100, - borderRadius: 4, - borderWidth: 1, - flex: 1, - fontSize: 16, - minHeight: 64, - paddingLeft: 10, - paddingVertical: 6 - } -}); - /** * PureComponent that supports editing and reviewing a transaction */ class TransactionEdit extends PureComponent { - static navigationOptions = ({ navigation }) => getTransactionOptionsTitle('send.title', 'Cancel', navigation); - static propTypes = { - /** - * List of accounts from the AccountTrackerController - */ - accounts: PropTypes.object, - /** - * Callback to warn if transaction to is a known contract address - */ - checkForAssetAddress: PropTypes.func, - /** - * react-navigation object used for switching between screens - */ - navigation: PropTypes.object, - /** - * Callback triggered when this transaction is cancelled - */ - onCancel: PropTypes.func, /** * Called when a user changes modes */ @@ -150,22 +31,10 @@ class TransactionEdit extends PureComponent { * Callback to update data in transaction in parent state */ handleUpdateData: PropTypes.func, - /** - * Callback to update from address in transaction in parent state - */ - handleUpdateFromAddress: PropTypes.func, /** * Callback to update readable value in transaction in parent state */ handleUpdateReadableValue: PropTypes.func, - /** - * Callback to update to address in transaction in parent state - */ - handleUpdateToAddress: PropTypes.func, - /** - * Callback to update selected asset in transaction in parent state - */ - handleUpdateAsset: PropTypes.func, /** * Callback to validate amount in transaction in parent state */ @@ -177,41 +46,15 @@ class TransactionEdit extends PureComponent { /** * Callback to validate to address in transaction in parent state */ - validateToAddress: PropTypes.func, - /** - * Object containing accounts balances - */ - contractBalances: PropTypes.object, - /** - * Indicates whether hex data should be shown in transaction editor - */ - showHexData: PropTypes.bool + validateToAddress: PropTypes.func }; state = { toFocused: false, amountError: '', - addressError: '', toAddressError: '', - toAddressWarning: '', gasError: '', - fillMax: false, - ensRecipient: undefined, - data: undefined, - accountSelectIsOpen: false, - ethInputIsOpen: false - }; - - openAccountSelect = isOpen => { - this.setState({ accountSelectIsOpen: isOpen, ethInputIsOpen: false }); - }; - - openEthInputIsOpen = isOpen => { - this.setState({ ethInputIsOpen: isOpen, accountSelectIsOpen: false }); - }; - - closeDropdowns = () => { - this.setState({ accountSelectIsOpen: false, ethInputIsOpen: false }); + data: undefined }; componentDidMount = () => { @@ -233,44 +76,6 @@ class TransactionEdit extends PureComponent { } }; - fillMax = () => { - const { gas, gasPrice, from, selectedAsset, assetType, paymentChannelTransaction } = this.props.transaction; - const { balance } = this.props.accounts[from]; - const { contractBalances } = this.props; - let value, readableValue; - if (assetType === 'ETH') { - const totalGas = isBN(gas) && isBN(gasPrice) ? gas.mul(gasPrice) : fromWei(0); - value = hexToBN(balance) - .sub(totalGas) - .gt(fromWei(0)) - ? hexToBN(balance).sub(totalGas) - : fromWei(0); - readableValue = fromWei(value); - } else if (paymentChannelTransaction) { - const state = PaymentChannelsClient.getState(); - value = toTokenMinimalUnit(state.balance, selectedAsset.decimals); - readableValue = state.balance; - } else if (assetType === 'ERC20') { - value = hexToBN(contractBalances[selectedAsset.address].toString(16)); - readableValue = fromTokenMinimalUnit(value, selectedAsset.decimals); - } - this.props.handleUpdateAmount(value); - this.props.handleUpdateReadableValue(readableValue); - this.setState({ fillMax: true }); - }; - - updateFillMax = fillMax => { - this.setState({ fillMax }); - }; - - updateToAddressError = error => { - this.setState({ toAddressError: error }); - }; - - onFocusToAddress = () => { - this.setState({ toFocused: true }); - }; - review = async () => { const { onModeChange } = this.props; const { data } = this.state; @@ -280,8 +85,8 @@ class TransactionEdit extends PureComponent { if (data && data.substr(0, 2) !== '0x') { this.updateData(addHexPrefix(data)); } - onModeChange && onModeChange('review'); } + onModeChange && onModeChange('review'); }; validate = async () => { @@ -292,13 +97,6 @@ class TransactionEdit extends PureComponent { return amountError || gasError || toAddressError; }; - updateAmount = async (amount, renderValue) => { - await this.props.handleUpdateAmount(amount); - this.props.handleUpdateReadableValue(renderValue); - const amountError = await this.props.validateAmount(true); - this.setState({ amountError }); - }; - updateGas = async (gas, gasLimit) => { await this.props.handleGasFeeSelection(gas, gasLimit); const gasError = this.props.validateGas(); @@ -310,165 +108,26 @@ class TransactionEdit extends PureComponent { this.props.handleUpdateData(data); }; - updateFromAddress = from => { - this.props.handleUpdateFromAddress(from); - }; - - updateToAddress = async to => { - await this.props.handleUpdateToAddress(to); - this.setState({ toAddressError: undefined }); - }; - - updateAndValidateToAddress = async (to, ensRecipient) => { - await this.props.handleUpdateToAddress(to, ensRecipient); - let { toAddressError, toAddressWarning } = this.state; - toAddressError = toAddressError || this.props.validateToAddress(); - toAddressWarning = toAddressWarning || this.props.checkForAssetAddress(); - this.setState({ toAddressError, toAddressWarning, ensRecipient }); - }; - - renderAmountLabel = () => { - const { amountError } = this.state; - const { assetType } = this.props.transaction; - if (assetType !== 'ERC721') { - return ( - - {strings('transaction.amount')}: - {amountError ? ( - {amountError} - ) : ( - - {strings('transaction.max')} - - )} - - ); - } - return ( - - Collectible: - {amountError ? {amountError} : undefined} - - ); - }; - render() { const { - navigation, - transaction: { - value, - gas, - gasPrice, - from, - to, - selectedAsset, - readableValue, - ensRecipient, - paymentChannelTransaction - }, - showHexData + transaction: { gas, gasPrice } } = this.props; - const { gasError, toAddressError, toAddressWarning, data, accountSelectIsOpen, ethInputIsOpen } = this.state; + const { gasError } = this.state; const totalGas = isBN(gas) && isBN(gasPrice) ? gas.mul(gasPrice) : toBN('0x0'); return ( - - - - - - {strings('transaction.from')}: - - - - - {this.renderAmountLabel()} - - - - - {strings('transaction.to')}: - {toAddressError ? {toAddressError} : null} - {!toAddressError && toAddressWarning ? ( - {toAddressWarning} - ) : null} - - - - {!paymentChannelTransaction && ( - <> - - - - - {showHexData && ( - - {strings('transaction.hex_data')}: - - )} - {showHexData && ( - - )} - - - )} - - - + ); } } const mapStateToProps = state => ({ - accounts: state.engine.backgroundState.AccountTrackerController.accounts, - contractBalances: state.engine.backgroundState.TokenBalancesController.contractBalances, - showHexData: state.settings.showHexData, transaction: getNormalizedTxState(state) }); diff --git a/app/components/UI/TransactionEditor/__snapshots__/index.test.js.snap b/app/components/UI/TransactionEditor/__snapshots__/index.test.js.snap index 9436176b551..3523637f1c9 100644 --- a/app/components/UI/TransactionEditor/__snapshots__/index.test.js.snap +++ b/app/components/UI/TransactionEditor/__snapshots__/index.test.js.snap @@ -1,12 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`TransactionEditor should render correctly 1`] = ` - -`; +exports[`TransactionEditor should render correctly 1`] = ``; diff --git a/app/components/UI/TransactionEditor/index.js b/app/components/UI/TransactionEditor/index.js index 41130974e26..0c36516c8f7 100644 --- a/app/components/UI/TransactionEditor/index.js +++ b/app/components/UI/TransactionEditor/index.js @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; +import { View, StyleSheet } from 'react-native'; import PropTypes from 'prop-types'; -import { StyleSheet, View } from 'react-native'; import { colors } from '../../../styles/common'; import ConfirmSend from '../../Views/SendFlow/Confirm'; import TransactionReview from '../TransactionReview'; @@ -17,14 +17,29 @@ import contractMap from 'eth-contract-metadata'; import PaymentChannelsClient from '../../../core/PaymentChannelsClient'; import { safeToChecksumAddress } from '../../../util/address'; import TransactionTypes from '../../../core/TransactionTypes'; +import Device from '../../../util/Device'; const EDIT = 'edit'; const REVIEW = 'review'; const styles = StyleSheet.create({ - root: { + reviewRoot: { backgroundColor: colors.white, - flex: 1 + minHeight: 200, + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + paddingTop: 24, + paddingBottom: Device.isIphoneX() ? 24 : 0, + justifyContent: 'flex-end' + }, + editRoot: { + backgroundColor: colors.white, + minHeight: 200, + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + paddingTop: 24, + paddingBottom: Device.isIphoneX() ? 44 : 24, + justifyContent: 'flex-end' } }); @@ -573,36 +588,40 @@ class TransactionEditor extends PureComponent { const { mode, transactionConfirmed, transaction } = this.props; return ( - + {mode === EDIT && transaction.paymentChannelTransaction && } {mode === EDIT && !transaction.paymentChannelTransaction && ( - + + + )} {mode === REVIEW && ( - + + + )} - + ); }; } diff --git a/app/components/UI/TransactionReview/TransactionReviewData/__snapshots__/index.test.js.snap b/app/components/UI/TransactionReview/TransactionReviewData/__snapshots__/index.test.js.snap index 7820fefeda6..74b841eee08 100644 --- a/app/components/UI/TransactionReview/TransactionReviewData/__snapshots__/index.test.js.snap +++ b/app/components/UI/TransactionReview/TransactionReviewData/__snapshots__/index.test.js.snap @@ -4,104 +4,150 @@ exports[`TransactionReviewData should render correctly 1`] = ` + + + + + Data + + + + + Data associated with this transaction + + - FUNCTION TYPE + Function + : + - - - HEX DATA + Hex data : + - - + /> + `; diff --git a/app/components/UI/TransactionReview/TransactionReviewData/index.js b/app/components/UI/TransactionReview/TransactionReviewData/index.js index fef3d57ab05..08b50acb187 100644 --- a/app/components/UI/TransactionReview/TransactionReviewData/index.js +++ b/app/components/UI/TransactionReview/TransactionReviewData/index.js @@ -1,44 +1,62 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { StyleSheet, Text, View, TextInput } from 'react-native'; +import { StyleSheet, Text, View, TouchableOpacity } from 'react-native'; +import IonicIcon from 'react-native-vector-icons/Ionicons'; import { colors, fontStyles } from '../../../../styles/common'; import { strings } from '../../../../../locales/i18n'; import { connect } from 'react-redux'; const styles = StyleSheet.create({ overview: { - paddingHorizontal: 24 + paddingHorizontal: 24, + marginBottom: 24 }, - label: { - flex: 0, - paddingRight: 18 + dataHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', + marginBottom: 28 }, - labelText: { + dataTitleText: { ...fontStyles.bold, - color: colors.grey400, - fontSize: 12 + color: colors.black, + fontSize: 14, + alignSelf: 'center' }, - functionType: { + dataDescription: { + textAlign: 'center', ...fontStyles.normal, color: colors.black, - fontSize: 12, - padding: 16 + fontSize: 14, + marginBottom: 28 + }, + dataBox: { + padding: 12, + borderWidth: 1, + borderColor: colors.grey200, + borderRadius: 8 + }, + hexDataContainer: {}, + label: { + flexDirection: 'row', + justifyContent: 'flex-start', + marginBottom: 12 + }, + boldLabel: { + ...fontStyles.bold + }, + labelText: { + ...fontStyles.normal, + color: colors.black, + fontSize: 14 }, hexData: { ...fontStyles.normal, backgroundColor: colors.white, color: colors.black, - flex: 1, - fontSize: 12, - minHeight: 64, - padding: 16 - }, - topOverviewRow: { - borderBottomWidth: 1, - borderColor: colors.grey200 - }, - overviewRow: { - paddingVertical: 15 + fontSize: 14, + paddingTop: 0 } }); @@ -54,36 +72,44 @@ class TransactionReviewData extends PureComponent { /** * Transaction corresponding action key */ - actionKey: PropTypes.string + actionKey: PropTypes.string, + /** + * Hides or shows transaction data + */ + toggleDataView: PropTypes.func }; render = () => { const { transaction: { data }, - actionKey + actionKey, + toggleDataView } = this.props; return ( - {actionKey !== strings('transactions.tx_review_confirm') && ( - + + + + + {strings('transaction.data')} + + + {strings('transaction.data_description')} + + {actionKey !== strings('transactions.tx_review_confirm') && ( - {strings('transaction.review_function_type')} - {actionKey} + + {strings('transaction.review_function')}:{' '} + + {actionKey} + )} + + + {strings('transaction.review_hex_data')}:{' '} + + {data} - )} - - - {strings('transaction.review_hex_data')}: - - ); diff --git a/app/components/UI/TransactionReview/TransactionReviewFeeCard/__snapshots__/index.test.js.snap b/app/components/UI/TransactionReview/TransactionReviewFeeCard/__snapshots__/index.test.js.snap index 9fb0bd45533..5d11a7f3627 100644 --- a/app/components/UI/TransactionReview/TransactionReviewFeeCard/__snapshots__/index.test.js.snap +++ b/app/components/UI/TransactionReview/TransactionReviewFeeCard/__snapshots__/index.test.js.snap @@ -4,20 +4,28 @@ exports[`TransactionReviewFeeCard should render correctly 1`] = ` - - - Amount - - + - + } + /> + + - + Network fee + + - Network fee + Edit - - - Edit - - - - + + - - + } + > + + + - - - Total - - Amount - - + Total + + Amount + + + - - - + diff --git a/app/components/UI/TransactionReview/TransactionReviewFeeCard/index.js b/app/components/UI/TransactionReview/TransactionReviewFeeCard/index.js index d23335ffa8f..e7b1be6abe7 100644 --- a/app/components/UI/TransactionReview/TransactionReviewFeeCard/index.js +++ b/app/components/UI/TransactionReview/TransactionReviewFeeCard/index.js @@ -5,16 +5,12 @@ import { colors, fontStyles } from '../../../../styles/common'; import { strings } from '../../../../../locales/i18n'; const styles = StyleSheet.create({ - overviewWrapper: { - flexDirection: 'row', - justifyContent: 'center' - }, overview: { - width: '90%', borderWidth: 1, borderColor: colors.grey200, borderRadius: 10, - padding: 16 + padding: 16, + marginHorizontal: 24 }, overviewAccent: { color: colors.blue @@ -27,10 +23,10 @@ const styles = StyleSheet.create({ topOverviewCol: { borderBottomWidth: 1, borderColor: colors.grey200, - paddingBottom: 10 + paddingBottom: 12 }, bottomOverviewCol: { - paddingTop: 10 + paddingTop: 12 }, amountRow: { width: '100%', @@ -39,7 +35,7 @@ const styles = StyleSheet.create({ alignItems: 'center' }, amountRowBottomSpace: { - paddingBottom: 10 + paddingBottom: 12 }, totalValueRow: { justifyContent: 'flex-end' @@ -58,7 +54,8 @@ const styles = StyleSheet.create({ paddingRight: 5 }, totalValueText: { - color: colors.fontSecondary + color: colors.fontSecondary, + textTransform: 'uppercase' }, loader: { backgroundColor: colors.white, @@ -94,7 +91,7 @@ class TransactionReviewFeeCard extends PureComponent { /** * Total transaction value in ETH */ - totalValue: PropTypes.string, + totalValue: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), /** * Transaction value in ETH before gas fee */ @@ -148,42 +145,37 @@ class TransactionReviewFeeCard extends PureComponent { equivalentTotalAmount = totalValue; } return ( - - - - - {strings('transaction.amount')} - {amount} - - - - - {strings('transaction.gas_fee')} - - - - {strings('transaction.edit')} - - - - {this.renderIfGasEstimationReady({networkFee})} - + + + + {strings('transaction.amount')} + {amount} - - - - {strings('transaction.total')} {strings('transaction.amount')} + + + + {strings('transaction.gas_fee')} - {!!totalFiat && - this.renderIfGasEstimationReady({totalAmount})} - - - {this.renderIfGasEstimationReady( - - {equivalentTotalAmount} + + + {strings('transaction.edit')} - )} + + {this.renderIfGasEstimationReady({networkFee})} + + + + + + {strings('transaction.total')} {strings('transaction.amount')} + + {!!totalFiat && this.renderIfGasEstimationReady(totalAmount)} + + + {this.renderIfGasEstimationReady( + {equivalentTotalAmount} + )} diff --git a/app/components/UI/TransactionReview/TransactionReviewInformation/__snapshots__/index.test.js.snap b/app/components/UI/TransactionReview/TransactionReviewInformation/__snapshots__/index.test.js.snap index 1a0592ad43a..5b8f396ad55 100644 --- a/app/components/UI/TransactionReview/TransactionReviewInformation/__snapshots__/index.test.js.snap +++ b/app/components/UI/TransactionReview/TransactionReviewInformation/__snapshots__/index.test.js.snap @@ -1,117 +1,44 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`TransactionReviewInformation should render correctly 1`] = ` - - - - Network fee - - - - $0 - - - < 0.00001 ETH - - - + + - - Total - - - Amount + Network fee + View Data - + - + `; diff --git a/app/components/UI/TransactionReview/TransactionReviewInformation/index.js b/app/components/UI/TransactionReview/TransactionReviewInformation/index.js index 0255076348b..d97087dffff 100644 --- a/app/components/UI/TransactionReview/TransactionReviewInformation/index.js +++ b/app/components/UI/TransactionReview/TransactionReviewInformation/index.js @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import PropTypes from 'prop-types'; -import { StyleSheet, Text, View } from 'react-native'; +import { StyleSheet, Text, View, TouchableOpacity } from 'react-native'; import { colors, fontStyles } from '../../../../styles/common'; import { connect } from 'react-redux'; import { @@ -15,18 +15,9 @@ import { } from '../../../../util/number'; import { strings } from '../../../../../locales/i18n'; import { getTicker, getNormalizedTxState } from '../../../../util/transactions'; +import TransactionReviewFeeCard from '../TransactionReviewFeeCard'; const styles = StyleSheet.create({ - overview: { - paddingHorizontal: 24, - borderTopWidth: 1, - borderColor: colors.grey200 - }, - overviewRow: { - alignItems: 'center', - flexDirection: 'row', - paddingVertical: 15 - }, overviewAlert: { alignItems: 'center', backgroundColor: colors.red000, @@ -35,7 +26,9 @@ const styles = StyleSheet.create({ borderWidth: 1, flexDirection: 'row', height: 32, - paddingHorizontal: 16 + paddingHorizontal: 16, + marginHorizontal: 24, + marginTop: 12 }, overviewAlertText: { ...fontStyles.normal, @@ -48,18 +41,6 @@ const styles = StyleSheet.create({ color: colors.red, flex: 0 }, - topOverviewRow: { - borderBottomWidth: 1, - borderColor: colors.grey200 - }, - overviewLabel: { - ...fontStyles.bold, - color: colors.grey500, - flex: 1, - fontSize: 12, - minWidth: 30, - textTransform: 'uppercase' - }, overviewPrimary: { ...fontStyles.bold, color: colors.fontPrimary, @@ -72,16 +53,8 @@ const styles = StyleSheet.create({ }, overviewEth: { ...fontStyles.normal, - color: colors.grey500, - fontSize: 16, - textAlign: 'right', - textTransform: 'uppercase' - }, - overviewInfo: { - ...fontStyles.normal, - color: colors.grey500, - fontSize: 12, - marginBottom: 6, + color: colors.fontPrimary, + fontSize: 14, textAlign: 'right', textTransform: 'uppercase' }, @@ -95,6 +68,20 @@ const styles = StyleSheet.create({ }, collectibleName: { maxWidth: '30%' + }, + viewDataWrapper: { + marginTop: 32, + marginBottom: 16 + }, + viewDataButton: { + alignSelf: 'center' + }, + viewDataText: { + color: colors.blue, + textAlign: 'center', + fontSize: 12, + ...fontStyles.bold, + alignSelf: 'center' } }); @@ -126,7 +113,23 @@ class TransactionReviewInformation extends PureComponent { /** * Current provider ticker */ - ticker: PropTypes.string + ticker: PropTypes.string, + /** + * Transaction amount in selected asset before gas + */ + assetAmount: PropTypes.string, + /** + * Transaction amount in fiat before gas + */ + fiatValue: PropTypes.string, + /** + * ETH or fiat, depending on user setting + */ + primaryCurrency: PropTypes.string, + /** + * Hides or shows transaction data + */ + toggleDataView: PropTypes.func }; state = { @@ -234,32 +237,20 @@ class TransactionReviewInformation extends PureComponent { render() { const { amountError, totalGasFiat, totalGasEth, totalFiat, totalValue } = this.state; - const gasPrimary = totalGasFiat || totalGasEth; - const gasSeconday = totalGasFiat ? totalGasEth : null; - + const { fiatValue, assetAmount, primaryCurrency, toggleDataView } = this.props; return ( - - - {strings('transaction.gas_fee')} - - {gasPrimary} - {!!gasSeconday && {totalGasEth}} - - - - - {strings('transaction.total')} - - - {`${strings('transaction.amount')} + ${strings('transaction.gas_fee')}`} - - {!!totalFiat && ( - {totalFiat} - )} - {totalValue} - - - + + {!!amountError && ( @@ -268,7 +259,12 @@ class TransactionReviewInformation extends PureComponent { )} - + + + {strings('transaction.view_data')} + + + ); } } @@ -278,7 +274,8 @@ const mapStateToProps = state => ({ currentCurrency: state.engine.backgroundState.CurrencyRateController.currentCurrency, contractExchangeRates: state.engine.backgroundState.TokenRatesController.contractExchangeRates, transaction: getNormalizedTxState(state), - ticker: state.engine.backgroundState.NetworkController.provider.ticker + ticker: state.engine.backgroundState.NetworkController.provider.ticker, + primaryCurrency: state.settings.primaryCurrency }); export default connect(mapStateToProps)(TransactionReviewInformation); diff --git a/app/components/UI/TransactionReview/TransactionReviewInformation/index.test.js b/app/components/UI/TransactionReview/TransactionReviewInformation/index.test.js index 3f9fcff93a4..0e6a69326a0 100644 --- a/app/components/UI/TransactionReview/TransactionReviewInformation/index.test.js +++ b/app/components/UI/TransactionReview/TransactionReviewInformation/index.test.js @@ -36,6 +36,9 @@ describe('TransactionReviewInformation', () => { to: '0x2', selectedAsset: undefined, assetType: undefined + }, + settings: { + primaryCurrency: 'ETH' } }; diff --git a/app/components/UI/TransactionReview/TransactionReviewSummary/__snapshots__/index.test.js.snap b/app/components/UI/TransactionReview/TransactionReviewSummary/__snapshots__/index.test.js.snap index 9da1e1b1c48..f37d4c9950e 100644 --- a/app/components/UI/TransactionReview/TransactionReviewSummary/__snapshots__/index.test.js.snap +++ b/app/components/UI/TransactionReview/TransactionReviewSummary/__snapshots__/index.test.js.snap @@ -1,18 +1,15 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`TransactionReviewSummary should render correctly 1`] = ` - + @@ -22,15 +19,15 @@ exports[`TransactionReviewSummary should render correctly 1`] = ` Object { "alignItems": "center", "borderColor": "#848c96", - "borderRadius": 4, + "borderRadius": 12, "borderWidth": 1, - "color": "#848c96", + "color": "#000000", "fontFamily": "CircularStd-Medium", - "fontSize": 12, + "fontSize": 10, "fontWeight": "400", - "lineHeight": 22, + "paddingHorizontal": 8, + "paddingVertical": 4, "textAlign": "center", - "width": "50%", } } /> @@ -41,7 +38,9 @@ exports[`TransactionReviewSummary should render correctly 1`] = ` "fontFamily": "CircularStd-Medium", "fontSize": 44, "fontWeight": "400", - "paddingVertical": 4, + "paddingBottom": 4, + "paddingTop": 16, + "textAlign": "center", "textTransform": "uppercase", } } diff --git a/app/components/UI/TransactionReview/TransactionReviewSummary/index.js b/app/components/UI/TransactionReview/TransactionReviewSummary/index.js index e7db15148ad..fa760eb9ada 100644 --- a/app/components/UI/TransactionReview/TransactionReviewSummary/index.js +++ b/app/components/UI/TransactionReview/TransactionReviewSummary/index.js @@ -1,71 +1,50 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { StyleSheet, Text, View } from 'react-native'; -import { - weiToFiat, - balanceToFiat, - renderFromTokenMinimalUnit, - renderFromWei, - fromTokenMinimalUnit -} from '../../../../util/number'; -import { colors, fontStyles, baseStyles } from '../../../../styles/common'; +import { colors, fontStyles } from '../../../../styles/common'; import { strings } from '../../../../../locales/i18n'; -import { connect } from 'react-redux'; -import { - APPROVE_FUNCTION_SIGNATURE, - decodeTransferData, - getTicker, - getNormalizedTxState -} from '../../../../util/transactions'; -import contractMap from 'eth-contract-metadata'; -import IonicIcon from 'react-native-vector-icons/Ionicons'; -import { safeToChecksumAddress } from '../../../../util/address'; +import WarningMessage from '../../../Views/SendFlow/WarningMessage'; const styles = StyleSheet.create({ confirmBadge: { ...fontStyles.normal, alignItems: 'center', borderColor: colors.grey400, - borderRadius: 4, + borderRadius: 12, borderWidth: 1, - color: colors.grey400, - fontSize: 12, - lineHeight: 22, - textAlign: 'center', - width: '50%' + color: colors.black, + fontSize: 10, + paddingVertical: 4, + paddingHorizontal: 8, + textAlign: 'center' }, summary: { backgroundColor: colors.beige, - padding: 24 + padding: 24, + paddingTop: 12, + paddingBottom: 16, + alignItems: 'center' }, - summaryFiat: { + summaryPrimary: { ...fontStyles.normal, color: colors.fontPrimary, fontSize: 44, - paddingVertical: 4, - textTransform: 'uppercase' + paddingTop: 16, + paddingBottom: 4, + textTransform: 'uppercase', + textAlign: 'center' }, - summaryEth: { + summarySecondary: { ...fontStyles.normal, - color: colors.grey400, + color: colors.black, fontSize: 24, - textTransform: 'uppercase' + textTransform: 'uppercase', + textAlign: 'center' }, warning: { - flex: 1, - flexDirection: 'row', - borderColor: colors.grey400, - borderBottomWidth: 1, - padding: 16, - backgroundColor: colors.yellow100 - }, - warningText: { - flex: 1, - ...fontStyles.normal, - marginHorizontal: 8, - color: colors.grey500, - textAlign: 'left', - fontSize: 12 + width: '100%', + paddingHorizontal: 24, + paddingTop: 12 } }); @@ -74,111 +53,41 @@ const styles = StyleSheet.create({ */ class TransactionReviewSummary extends PureComponent { static propTypes = { - /** - * Transaction object associated with this transaction - */ - transaction: PropTypes.object, /** * ETH to current currency conversion rate */ conversionRate: PropTypes.number, /** - * Currency code of the currently-active currency + * Transaction corresponding action key */ - currentCurrency: PropTypes.string, + actionKey: PropTypes.string, /** - * Object containing token exchange rates in the format address => exchangeRate + * Transaction amount in ETH before gas */ - contractExchangeRates: PropTypes.object, + assetAmount: PropTypes.string, /** - * Transaction corresponding action key + * Transaction amount in fiat before gas */ - actionKey: PropTypes.string, + fiatValue: PropTypes.string, /** - * Array of ERC20 assets + * Approve type transaction or not */ - tokens: PropTypes.array, + approveTransaction: PropTypes.bool, /** - * Current provider ticker + * ETH or fiat, depending on user setting */ - ticker: PropTypes.string + primaryCurrency: PropTypes.string }; - state = { - assetAmount: undefined, - conversionRate: undefined, - fiatValue: undefined - }; - - componentDidMount = () => { - const { - transaction: { data, to }, - tokens - } = this.props; - let assetAmount, conversionRate, fiatValue; - const approveTransaction = data && data.substr(0, 10) === APPROVE_FUNCTION_SIGNATURE; - if (approveTransaction) { - let contract = contractMap[safeToChecksumAddress(to)]; - if (!contract) { - contract = tokens.find(({ address }) => address === safeToChecksumAddress(to)); - } - const symbol = (contract && contract.symbol) || 'ERC20'; - assetAmount = `${decodeTransferData('transfer', data)[1]} ${symbol}`; - } else { - [assetAmount, conversionRate, fiatValue] = this.getRenderValues()(); - } - this.setState({ assetAmount, conversionRate, fiatValue, approveTransaction }); - }; - - getRenderValues = () => { - const { - transaction: { value, selectedAsset, assetType }, - currentCurrency, - contractExchangeRates, - ticker - } = this.props; - const values = { - ETH: () => { - const assetAmount = `${renderFromWei(value)} ${getTicker(ticker)}`; - const conversionRate = this.props.conversionRate; - const fiatValue = weiToFiat(value, conversionRate, currentCurrency); - return [assetAmount, conversionRate, fiatValue]; - }, - ERC20: () => { - const assetAmount = `${renderFromTokenMinimalUnit(value, selectedAsset.decimals)} ${ - selectedAsset.symbol - }`; - const conversionRate = contractExchangeRates[selectedAsset.address]; - const fiatValue = balanceToFiat( - (value && fromTokenMinimalUnit(value, selectedAsset.decimals)) || 0, - this.props.conversionRate, - conversionRate, - currentCurrency - ); - return [assetAmount, conversionRate, fiatValue]; - }, - ERC721: () => { - const assetAmount = strings('unit.token_id') + selectedAsset.tokenId; - const conversionRate = true; - const fiatValue = selectedAsset.name; - return [assetAmount, conversionRate, fiatValue]; - }, - default: () => [undefined, undefined, undefined] - }; - return values[assetType] || values.default; - }; + renderWarning = () => {`${strings('transaction.approve_warning')} ${this.props.assetAmount}`}; render = () => { - const { actionKey } = this.props; - const { assetAmount, conversionRate, fiatValue, approveTransaction } = this.state; + const { actionKey, assetAmount, conversionRate, fiatValue, approveTransaction, primaryCurrency } = this.props; return ( - + {!!approveTransaction && ( - - {`${strings( - 'transaction.approve_warning' - )} ${assetAmount}`} + )} @@ -187,11 +96,15 @@ class TransactionReviewSummary extends PureComponent { {!conversionRate ? ( - {assetAmount} + {assetAmount} ) : ( - {fiatValue} - {assetAmount} + + {primaryCurrency === 'ETH' ? assetAmount : fiatValue} + + + {primaryCurrency === 'ETH' ? fiatValue : assetAmount} + )} @@ -200,13 +113,4 @@ class TransactionReviewSummary extends PureComponent { }; } -const mapStateToProps = state => ({ - conversionRate: state.engine.backgroundState.CurrencyRateController.conversionRate, - currentCurrency: state.engine.backgroundState.CurrencyRateController.currentCurrency, - contractExchangeRates: state.engine.backgroundState.TokenRatesController.contractExchangeRates, - tokens: state.engine.backgroundState.AssetsController.tokens, - transaction: getNormalizedTxState(state), - ticker: state.engine.backgroundState.NetworkController.provider.ticker -}); - -export default connect(mapStateToProps)(TransactionReviewSummary); +export default TransactionReviewSummary; diff --git a/app/components/UI/TransactionReview/__snapshots__/index.test.js.snap b/app/components/UI/TransactionReview/__snapshots__/index.test.js.snap index 1333e01084a..daec782932d 100644 --- a/app/components/UI/TransactionReview/__snapshots__/index.test.js.snap +++ b/app/components/UI/TransactionReview/__snapshots__/index.test.js.snap @@ -1,50 +1,59 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`TransactionReview should render correctly 1`] = ` - + + + - - - - + - + + - - - + + + `; diff --git a/app/components/UI/TransactionReview/index.js b/app/components/UI/TransactionReview/index.js index afa2d31aca7..c7480de7162 100644 --- a/app/components/UI/TransactionReview/index.js +++ b/app/components/UI/TransactionReview/index.js @@ -1,31 +1,37 @@ import React, { PureComponent } from 'react'; -import ActionView from '../ActionView'; import PropTypes from 'prop-types'; import { StyleSheet, Text, View, InteractionManager } from 'react-native'; import { colors, fontStyles } from '../../../styles/common'; import { connect } from 'react-redux'; import { strings } from '../../../../locales/i18n'; -import { getTransactionReviewActionKey, getNormalizedTxState } from '../../../util/transactions'; -import ScrollableTabView from 'react-native-scrollable-tab-view'; +import { + getTransactionReviewActionKey, + getNormalizedTxState, + APPROVE_FUNCTION_SIGNATURE, + decodeTransferData, + getTicker +} from '../../../util/transactions'; +import { + weiToFiat, + balanceToFiat, + renderFromTokenMinimalUnit, + renderFromWei, + fromTokenMinimalUnit +} from '../../../util/number'; +import { safeToChecksumAddress } from '../../../util/address'; +import Device from '../../../util/Device'; +import contractMap from 'eth-contract-metadata'; import DefaultTabBar from 'react-native-scrollable-tab-view/DefaultTabBar'; import TransactionReviewInformation from './TransactionReviewInformation'; -import TransactionReviewData from './TransactionReviewData'; import TransactionReviewSummary from './TransactionReviewSummary'; +import TransactionReviewData from './TransactionReviewData'; import Analytics from '../../../core/Analytics'; import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics'; -import TransactionDirection from '../../Views/TransactionDirection'; +import TransactionHeader from '../TransactionHeader'; +import AccountInfoCard from '../AccountInfoCard'; +import ActionView from '../ActionView'; const styles = StyleSheet.create({ - root: { - backgroundColor: colors.white, - flex: 1 - }, - reviewForm: { - flex: 1 - }, - overview: { - flex: 1 - }, tabUnderlineStyle: { height: 2, backgroundColor: colors.blue @@ -39,16 +45,31 @@ const styles = StyleSheet.create({ letterSpacing: 0.5, ...fontStyles.bold }, - error: { + actionViewWrapper: { + height: Device.isMediumDevice() ? 200 : 385 + }, + actionViewChildren: { + height: 300 + }, + accountInfoCardWrapper: { + paddingHorizontal: 24, + paddingBottom: 12 + }, + errorWrapper: { + marginHorizontal: 24, + marginBottom: 12, + paddingHorizontal: 10, + paddingVertical: 8, backgroundColor: colors.red000, + borderColor: colors.red, + borderRadius: 8, + borderWidth: 1, + justifyContent: 'center', + alignItems: 'center' + }, + error: { color: colors.red, - marginTop: 5, - paddingVertical: 8, - paddingHorizontal: 5, - textAlign: 'center', fontSize: 12, - letterSpacing: 0.5, - marginHorizontal: 14, ...fontStyles.normal } }); @@ -85,32 +106,115 @@ class TransactionReview extends PureComponent { /** * Callback to validate transaction in parent state */ - validate: PropTypes.func + validate: PropTypes.func, + /** + * Browser/tab information + */ + browser: PropTypes.object, + /** + * ETH to current currency conversion rate + */ + conversionRate: PropTypes.number, + /** + * Currency code of the currently-active currency + */ + currentCurrency: PropTypes.string, + /** + * Object containing token exchange rates in the format address => exchangeRate + */ + contractExchangeRates: PropTypes.object, + /** + * Array of ERC20 assets + */ + tokens: PropTypes.array, + /** + * Current provider ticker + */ + ticker: PropTypes.string, + /** + * ETH or fiat, depending on user setting + */ + primaryCurrency: PropTypes.string }; state = { toFocused: false, actionKey: strings('transactions.tx_review_confirm'), showHexData: false, - error: undefined + dataVisible: false, + error: undefined, + assetAmount: undefined, + conversionRate: undefined, + fiatValue: undefined }; componentDidMount = async () => { const { validate, transaction, - transaction: { data } + transaction: { data, to }, + tokens } = this.props; let { showHexData } = this.props; + let assetAmount, conversionRate, fiatValue; showHexData = showHexData || data; + const approveTransaction = data && data.substr(0, 10) === APPROVE_FUNCTION_SIGNATURE; const error = validate && (await validate()); const actionKey = await getTransactionReviewActionKey(transaction); - this.setState({ error, actionKey, showHexData }); + if (approveTransaction) { + let contract = contractMap[safeToChecksumAddress(to)]; + if (!contract) { + contract = tokens.find(({ address }) => address === safeToChecksumAddress(to)); + } + const symbol = (contract && contract.symbol) || 'ERC20'; + assetAmount = `${decodeTransferData('transfer', data)[1]} ${symbol}`; + } else { + [assetAmount, conversionRate, fiatValue] = this.getRenderValues()(); + } + this.setState({ error, actionKey, showHexData, assetAmount, conversionRate, fiatValue, approveTransaction }); InteractionManager.runAfterInteractions(() => { Analytics.trackEvent(ANALYTICS_EVENT_OPTS.TRANSACTIONS_CONFIRM_STARTED); }); }; + getRenderValues = () => { + const { + transaction: { value, selectedAsset, assetType }, + currentCurrency, + contractExchangeRates, + ticker + } = this.props; + const values = { + ETH: () => { + const assetAmount = `${renderFromWei(value)} ${getTicker(ticker)}`; + const conversionRate = this.props.conversionRate; + const fiatValue = weiToFiat(value, conversionRate, currentCurrency); + return [assetAmount, conversionRate, fiatValue]; + }, + ERC20: () => { + const assetAmount = `${renderFromTokenMinimalUnit(value, selectedAsset.decimals)} ${ + selectedAsset.symbol + }`; + const conversionRate = contractExchangeRates[selectedAsset.address]; + const fiatValue = balanceToFiat( + (value && fromTokenMinimalUnit(value, selectedAsset.decimals)) || 0, + this.props.conversionRate, + conversionRate, + currentCurrency + ); + return [assetAmount, conversionRate, fiatValue]; + }, + ERC721: () => { + const assetAmount = strings('unit.token_id') + selectedAsset.tokenId; + const conversionRate = true; + const fiatValue = selectedAsset.name; + return [assetAmount, conversionRate, fiatValue]; + }, + default: () => [undefined, undefined, undefined] + }; + return values[assetType] || values.default; + }; + edit = () => { const { onModeChange } = this.props; Analytics.trackEvent(ANALYTICS_EVENT_OPTS.TRANSACTIONS_EDIT_TRANSACTION); @@ -130,55 +234,90 @@ class TransactionReview extends PureComponent { ); } - renderTransactionDetails = () => { - const { showHexData, actionKey } = this.state; - const { transaction } = this.props; - return ( - - {showHexData && transaction.data ? ( - - - - - ) : ( - - )} - - ); + toggleDataView = () => { + this.setState({ dataVisible: !this.state.dataVisible }); }; + getUrlFromBrowser() { + const { browser } = this.props; + let url; + browser.tabs.forEach(tab => { + if (tab.id === browser.activeTab) { + url = tab.url; + } + }); + return url; + } + render = () => { - const { transactionConfirmed } = this.props; - const { actionKey, error } = this.state; - return ( - - - - - - {this.renderTransactionDetails()} - {!!error && {error}} + const { transactionConfirmed, primaryCurrency } = this.props; + const { + actionKey, + error, + assetAmount, + conversionRate, + fiatValue, + approveTransaction, + dataVisible + } = this.state; + const currentPageInformation = { url: this.getUrlFromBrowser() }; + const content = !dataVisible ? ( + <> + + + {!!error && ( + + {error} - - + )} + + + + + + + + + + + + ) : ( + ); + return <>{content}; }; } const mapStateToProps = state => ({ accounts: state.engine.backgroundState.AccountTrackerController.accounts, + tokens: state.engine.backgroundState.AssetsController.tokens, + currentCurrency: state.engine.backgroundState.CurrencyRateController.currentCurrency, + contractExchangeRates: state.engine.backgroundState.TokenRatesController.contractExchangeRates, + conversionRate: state.engine.backgroundState.CurrencyRateController.conversionRate, + ticker: state.engine.backgroundState.NetworkController.provider.ticker, showHexData: state.settings.showHexData, - transaction: getNormalizedTxState(state) + transaction: getNormalizedTxState(state), + browser: state.browser, + primaryCurrency: state.settings.primaryCurrency }); export default connect(mapStateToProps)(TransactionReview); diff --git a/app/components/UI/TransactionReview/index.test.js b/app/components/UI/TransactionReview/index.test.js index b02b3b8b84f..60f846fc440 100644 --- a/app/components/UI/TransactionReview/index.test.js +++ b/app/components/UI/TransactionReview/index.test.js @@ -15,11 +15,28 @@ describe('TransactionReview', () => { }, AccountTrackerController: { accounts: [] + }, + AssetsController: { + tokens: [] + }, + CurrencyRateController: { + currentCurrency: 'usd' + }, + TokenRatesController: { + contractExchangeRates: { + '0x': '0.1' + } + }, + NetworkController: { + provider: { + ticker: 'ETH' + } } } }, settings: { - showHexData: true + showHexData: true, + primaryCurrency: 'ETH' }, transaction: { value: '', @@ -30,6 +47,9 @@ describe('TransactionReview', () => { to: '0x2', selectedAsset: undefined, assetType: undefined + }, + browser: { + tabs: [] } }; diff --git a/app/components/Views/Approval/__snapshots__/index.test.js.snap b/app/components/Views/Approval/__snapshots__/index.test.js.snap index 4db97781134..c968ce8b76f 100644 --- a/app/components/Views/Approval/__snapshots__/index.test.js.snap +++ b/app/components/Views/Approval/__snapshots__/index.test.js.snap @@ -1,14 +1,50 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Approval should render correctly 1`] = ` - - + `; diff --git a/app/components/Views/Approval/index.js b/app/components/Views/Approval/index.js index 91ccd298847..ed3ab1b151b 100644 --- a/app/components/Views/Approval/index.js +++ b/app/components/Views/Approval/index.js @@ -1,11 +1,11 @@ import React, { PureComponent } from 'react'; -import { SafeAreaView, StyleSheet, Alert, InteractionManager } from 'react-native'; +import { StyleSheet, Alert, InteractionManager } from 'react-native'; import Engine from '../../../core/Engine'; import PropTypes from 'prop-types'; import TransactionEditor from '../../UI/TransactionEditor'; +import Modal from 'react-native-modal'; import { BNToHex, hexToBN } from '../../../util/number'; import { getTransactionOptionsTitle } from '../../UI/Navbar'; -import { colors } from '../../../styles/common'; import { resetTransaction } from '../../../actions/transaction'; import { connect } from 'react-redux'; import NotificationManager from '../../../core/NotificationManager'; @@ -21,9 +21,9 @@ const EDIT = 'edit'; const APPROVAL = 'Approval'; const styles = StyleSheet.create({ - wrapper: { - backgroundColor: colors.white, - flex: 1 + bottomModal: { + justifyContent: 'flex-end', + margin: 0 } }); @@ -53,7 +53,15 @@ class Approval extends PureComponent { /** * A string representing the network name */ - networkType: PropTypes.string + networkType: PropTypes.string, + /** + * Hides or shows the dApp transaction modal + */ + toggleDappTransactionModal: PropTypes.func, + /** + * Tells whether or not dApp transaction modal is visible + */ + dappTransactionModalVisible: PropTypes.bool }; state = { @@ -157,7 +165,7 @@ class Approval extends PureComponent { }; onCancel = () => { - this.props.navigation.pop(); + this.props.toggleDappTransactionModal(); this.state.mode === REVIEW && this.trackOnCancel(); this.showWalletConnectNotification(); }; @@ -182,7 +190,7 @@ class Approval extends PureComponent { TransactionController.hub.once(`${transaction.id}:finished`, transactionMeta => { if (transactionMeta.status === 'submitted') { this.setState({ transactionHandled: true }); - this.props.navigation.pop(); + this.props.toggleDappTransactionModal(); NotificationManager.watchSubmittedTransaction({ ...transactionMeta, assetType: transaction.assetType @@ -258,10 +266,23 @@ class Approval extends PureComponent { } render = () => { - const { transaction } = this.props; + const { transaction, dappTransactionModalVisible } = this.props; const { mode } = this.state; return ( - + - + ); }; } diff --git a/app/components/Views/Send/index.js b/app/components/Views/Send/index.js index 6baa11a0e2f..c9d56bc06ea 100644 --- a/app/components/Views/Send/index.js +++ b/app/components/Views/Send/index.js @@ -11,6 +11,7 @@ import { strings } from '../../../../locales/i18n'; import { getTransactionOptionsTitle } from '../../UI/Navbar'; import { connect } from 'react-redux'; import { resetTransaction, setTransactionObject } from '../../../actions/transaction'; +import { toggleDappTransactionModal } from '../../../actions/modals'; import NotificationManager from '../../../core/NotificationManager'; import NetworkList, { getNetworkTypeById } from '../../../util/networks'; import contractMap from 'eth-contract-metadata'; @@ -98,7 +99,15 @@ class Send extends PureComponent { /** * Object containing token balances in the format address => balance */ - contractBalances: PropTypes.object + contractBalances: PropTypes.object, + /** + /* Hides or shows dApp transaction modal + */ + toggleDappTransactionModal: PropTypes.func, + /** + /* dApp transaction modal visible or not + */ + dappTransactionModalVisible: PropTypes.bool }; state = { @@ -152,7 +161,9 @@ class Send extends PureComponent { const { navigation, transaction: { assetType, selectedAsset }, - contractBalances + contractBalances, + dappTransactionModalVisible, + toggleDappTransactionModal } = this.props; navigation && navigation.setParams({ @@ -160,6 +171,7 @@ class Send extends PureComponent { dispatch: this.onModeChange, disableModeChange: assetType === 'ERC20' && contractBalances[selectedAsset.address] === undefined }); + dappTransactionModalVisible && toggleDappTransactionModal(); this.mounted = true; await this.reset(); this.checkForDeeplinks(); @@ -644,13 +656,15 @@ const mapStateToProps = state => ({ tokens: state.engine.backgroundState.AssetsController.tokens, network: state.engine.backgroundState.NetworkController.network, identities: state.engine.backgroundState.PreferencesController.identities, - selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress + selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress, + dappTransactionModalVisible: state.modals.dappTransactionModalVisible }); const mapDispatchToProps = dispatch => ({ resetTransaction: () => dispatch(resetTransaction()), setTransactionObject: transaction => dispatch(setTransactionObject(transaction)), - showAlert: config => dispatch(showAlert(config)) + showAlert: config => dispatch(showAlert(config)), + toggleDappTransactionModal: () => dispatch(toggleDappTransactionModal()) }); export default connect( diff --git a/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap b/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap index 839dddd0cfc..ab1045d7d6a 100644 --- a/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap +++ b/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap @@ -91,7 +91,7 @@ exports[`Confirm should render correctly 1`] = ` toggleCustomGasModal={[Function]} totalFiat="" totalGasFiat="" - totalValue="" + totalValue={} transactionValue="" /> + {renderFromWei(transactionTotalAmountBN)} {parsedTicker} + + ); transactionTotalAmountFiat = weiToFiat(transactionTotalAmountBN, conversionRate, currentCurrency); transactionTo = to; } else if (selectedAsset.tokenId) { @@ -403,7 +414,11 @@ class Confirm extends PureComponent { } transactionValueFiat = weiToFiat(valueBN, conversionRate, currentCurrency); const transactionTotalAmountBN = weiTransactionFee && weiTransactionFee.add(valueBN); - transactionTotalAmount = `${renderFromWei(weiTransactionFee)} ${parsedTicker}`; + transactionTotalAmount = ( + + {renderFromWei(weiTransactionFee)} {parsedTicker} + + ); transactionTotalAmountFiat = weiToFiat(transactionTotalAmountBN, conversionRate, currentCurrency); } else { let amount; @@ -420,7 +435,11 @@ class Confirm extends PureComponent { transactionValueFiat = balanceToFiat(transferValue, conversionRate, exchangeRate, currentCurrency) || `0 ${currentCurrency}`; const transactionValueFiatNumber = balanceToFiatNumber(transferValue, conversionRate, exchangeRate); - transactionTotalAmount = `${transactionValue} + ${renderFromWei(weiTransactionFee)} ${parsedTicker}`; + transactionTotalAmount = ( + + {transactionValue} + ${renderFromWei(weiTransactionFee)} {parsedTicker} + + ); transactionTotalAmountFiat = renderFiatAddition( transactionValueFiatNumber, transactionFeeFiatNumber, @@ -871,7 +890,7 @@ class Confirm extends PureComponent { transactionFeeFiat = '', transactionFee, transactionTo = '', - transactionTotalAmount = '', + transactionTotalAmount = , transactionTotalAmountFiat = '', errorMessage, transactionConfirmed, diff --git a/app/components/Views/SendFlow/CustomGas/index.js b/app/components/Views/SendFlow/CustomGas/index.js index 4fdbac3eed2..ac526d15003 100644 --- a/app/components/Views/SendFlow/CustomGas/index.js +++ b/app/components/Views/SendFlow/CustomGas/index.js @@ -374,6 +374,7 @@ class CustomGas extends PureComponent { onGasLimitChange = value => { let warningGasLimit; const { customGasPrice } = this.state; + //Added because apiEstimateModifiedToWEI doesn't like empty strings const gasPrice = customGasPrice === '' ? '0' : customGasPrice; const bnValue = new BN(value); if (!value || value === '' || !isDecimal(value)) warningGasLimit = strings('transaction.invalid_gas'); diff --git a/app/reducers/modals/index.js b/app/reducers/modals/index.js index 05d69f6f2c4..fe4b9b9ebc1 100644 --- a/app/reducers/modals/index.js +++ b/app/reducers/modals/index.js @@ -3,7 +3,8 @@ const initialState = { accountsModalVisible: false, collectibleContractModalVisible: false, receiveModalVisible: false, - receiveAsset: undefined + receiveAsset: undefined, + dappTransactionModalVisible: false }; const modalsReducer = (state = initialState, action) => { @@ -30,6 +31,11 @@ const modalsReducer = (state = initialState, action) => { ...state, collectibleContractModalVisible: !state.collectibleContractModalVisible }; + case 'TOGGLE_DAPP_TRANSACTION_MODAL': + return { + ...state, + dappTransactionModalVisible: !state.dappTransactionModalVisible + }; default: return state; } diff --git a/locales/en.json b/locales/en.json index 37fd2fcf39f..16437b23191 100644 --- a/locales/en.json +++ b/locales/en.json @@ -490,8 +490,11 @@ "hex_data": "Hex Data", "review_details": "DETAILS", "review_data": "DATA", + "data": "Data", + "data_description": "Data associated with this transaction", "review_function_type": "FUNCTION TYPE", - "review_hex_data": "HEX DATA", + "review_function": "Function", + "review_hex_data": "Hex data", "insufficient": "Insufficient funds", "insufficient_tokens": "Insufficient {{token}}", "invalid_address": "Invalid address", @@ -527,6 +530,7 @@ "transaction_fee": "Network fee", "transaction_fee_less": "No fee", "total_amount": "Total amount", + "view_data": "View Data", "adjust_transaction_fee": "Adjust transaction fee", "could_not_resolve_ens": "Couldn't resolve ENS", "asset": "Asset", @@ -541,6 +545,7 @@ "gas_limit": "Gas Limit:", "gas_price": "Gas Price: (GWEI)", "save": "Save", + "total": "Total", "warning_gas_limit": "Gas limit must be greater than 20999 and less than 7920027", "cost_explanation": "Select the network fee you are willing to pay. The higher the fee, the better chances and faster your transaction will go through." },