diff --git a/react/src/actions/actions/nativeSend.js b/react/src/actions/actions/nativeSend.js index d68692d..3f96919 100644 --- a/react/src/actions/actions/nativeSend.js +++ b/react/src/actions/actions/nativeSend.js @@ -1,4 +1,7 @@ -import { DASHBOARD_ACTIVE_COIN_NATIVE_OPIDS } from '../storeType'; +import { + DASHBOARD_ACTIVE_COIN_NATIVE_OPIDS, + DASHBOARD_ACTIVE_COIN_SENDTO +} from '../storeType'; import { translate } from '../../translate/translate'; import { triggerToaster } from '../actionCreators'; import Config from '../../config'; @@ -106,6 +109,7 @@ export function sendNativeTx(coin, _payload) { ); } } else { + dispatch(sendToAddressState(JSON.parse(json).result)); dispatch( triggerToaster( translate('TOASTR.TX_SENT_ALT'), @@ -127,88 +131,41 @@ export function getKMDOPIDState(json) { // remove export function getKMDOPID(opid, coin) { - let tmpopidOutput = ''; - let ajaxDataToHex; - - if (opid === undefined) { - ajaxDataToHex = null; - } else { - ajaxDataToHex = `["${opid}"]`; - } - return dispatch => { - return iguanaHashHex(ajaxDataToHex, dispatch).then((hashHexJson) => { - if (hashHexJson === '5b226e756c6c225d00') { - hashHexJson = ''; - } - - let payload; - let passthruAgent = getPassthruAgent(coin); - let tmpIguanaRPCAuth = `tmpIgRPCUser@${sessionStorage.getItem('IguanaRPCAuth')}`; - - if (passthruAgent === 'iguana') { - payload = { - userpass: tmpIguanaRPCAuth, - agent: passthruAgent, - method: 'passthru', - asset: coin, - function: 'z_getoperationstatus', - hex: hashHexJson, - }; - } else { - payload = { - userpass: tmpIguanaRPCAuth, - agent: passthruAgent, - method: 'passthru', - function: 'z_getoperationstatus', - hex: hashHexJson, - }; - } - - let _fetchConfig = { - method: 'POST', - body: JSON.stringify(payload), - }; - - if (Config.cli.default) { - payload = { - mode: null, - chain: coin, - cmd: 'z_getoperationstatus', - }; + const payload = { + mode: null, + chain: coin, + cmd: 'z_getoperationstatus', + }; - _fetchConfig = { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ payload: payload }), - }; - } + const _fetchConfig = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ payload: payload }), + }; - fetch( - Config.cli.default ? `http://127.0.0.1:${Config.agamaPort}/shepherd/cli` : `http://127.0.0.1:${Config.iguanaCorePort}`, - _fetchConfig - ) - .catch(function(error) { - console.log(error); - dispatch( - triggerToaster( - 'getKMDOPID', - 'Error', - 'error' - ) - ); - }) - .then(response => response.json()) - .then(json => { - if (Config.cli.default) { - json = json.result; - } - dispatch(getKMDOPIDState(json)); - }) + fetch( + `http://127.0.0.1:${Config.agamaPort}/shepherd/cli`, + _fetchConfig + ) + .catch(function(error) { + console.log(error); + dispatch( + triggerToaster( + 'getKMDOPID', + 'Error', + 'error' + ) + ); }) - } + .then(response => response.json()) + .then(json => { + json = json.result; + dispatch(getKMDOPIDState(json)); + }) + }; } export function sendToAddressPromise(coin, address, amount) { @@ -253,4 +210,18 @@ export function sendToAddressPromise(coin, address, amount) { resolve(json); }); }); +} + +export function sendToAddressState(json) { + return { + type: DASHBOARD_ACTIVE_COIN_SENDTO, + lastSendToResponse: json, + } +} + +export function clearLastSendToResponseState() { + return { + type: DASHBOARD_ACTIVE_COIN_SENDTO, + lastSendToResponse: null, + } } \ No newline at end of file diff --git a/react/src/components/dashboard/_sendCoin/sendCoin.js b/react/src/components/dashboard/_sendCoin/sendCoin.js new file mode 100644 index 0000000..d7cf602 --- /dev/null +++ b/react/src/components/dashboard/_sendCoin/sendCoin.js @@ -0,0 +1,917 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import Config from '../../../config'; +import { translate } from '../../../translate/translate'; +import { checkTimestamp } from '../../../util/time'; +import { + edexGetTxIDList, + edexRemoveTXID +} from '../../../util/cacheFormat'; +import { + resolveOpenAliasAddress, + triggerToaster, + shepherdGroomPostPromise, + edexGetTransaction, + getCacheFile, + fetchUtxoCache, + sendToAddress, + iguanaUTXORawTX, + clearLastSendToResponseState, + sendToAddressStateAlt, + dexSendRawTX +} from '../../../actions/actionCreators'; +import Store from '../../../store'; +import { + UTXOCacheInfoRender, + SendCoinResponseRender, + OASendUIRender, + SendApiTypeSelectorRender, + SendCoinRender +} from './sendCoin.render'; + +import io from 'socket.io-client'; +import { isPositiveNumber } from '../../../util/number'; +const socket = io.connect(`http://127.0.0.1:${Config.agamaPort}`); + +// TODO: prevent any cache updates rather than utxo while on send coin form +// fix a bug - total amount is incorrect when switching between steps + +class SendCoin extends React.Component { + constructor(props) { + super(props); + this.state = { + currentStep: 0, + sendFrom: this.props.Dashboard.activeHandle ? this.props.Dashboard.activeHandle[this.props.ActiveCoin.coin] : null, + sendFromAmount: 0, + sendTo: '', + sendToOA: null, + amount: 0, + fee: 0.0001, + sendSig: false, + sendApiType: true, + addressSelectorOpen: false, + currentStackLength: 0, + totalStackLength: 0, + utxoMethodInProgress: false, + }; + this.updateInput = this.updateInput.bind(this); + this.handleBasiliskSend = this.handleBasiliskSend.bind(this); + this.openDropMenu = this.openDropMenu.bind(this); + this.toggleSendSig = this.toggleSendSig.bind(this); + this.getOAdress = this.getOAdress.bind(this); + this.toggleSendAPIType = this.toggleSendAPIType.bind(this); + this._fetchNewUTXOData = this._fetchNewUTXOData.bind(this); + this.handleClickOutside = this.handleClickOutside.bind(this); + this.setRecieverFromScan = this.setRecieverFromScan.bind(this); + socket.on('messages', msg => this.updateSocketsData(msg)); + } + + setRecieverFromScan(receiver) { + try { + const recObj = JSON.parse(receiver); + + if (recObj && + typeof recObj === 'object') { + if (recObj.coin === this.props.ActiveCoin.coin) { + if (recObj.amount) { + this.setState({ + amount: recObj.amount, + }); + } + if (recObj.address) { + this.setState({ + sendTo: recObj.address, + }); + } + } else { + Store.dispatch( + triggerToaster( + translate('SEND.QR_COIN_MISMATCH_MESSAGE_IMPORT_COIN') + + recObj.coin + + translate('SEND.QR_COIN_MISMATCH_MESSAGE_ACTIVE_COIN') + + this.props.ActiveCoin.coin + + translate('SEND.QR_COIN_MISMATCH_MESSAGE_END'), + translate('SEND.QR_COIN_MISMATCH_TITLE'), + 'warning' + ) + ); + } + } + } catch (e) { + this.setState({ + sendTo: receiver, + }); + } + + document.getElementById('edexcoinSendTo').focus(); + } + + componentWillMount() { + document.addEventListener( + 'click', + this.handleClickOutside, + false + ); + } + + componentWillUnmount() { + document.removeEventListener( + 'click', + this.handleClickOutside, + false + ); + } + + handleClickOutside(e) { + if (e.srcElement.className !== 'btn dropdown-toggle btn-info' && + (e.srcElement.offsetParent && e.srcElement.offsetParent.className !== 'btn dropdown-toggle btn-info') && + (e.path && e.path[4] && e.path[4].className.indexOf('showkmdwalletaddrs') === -1)) { + this.setState({ + addressSelectorOpen: false, + }); + } + } + + componentWillReceiveProps(props) { + if (this.state && + !this.state.sendFrom && + this.props.ActiveCoin.activeAddress) { + this.setState(Object.assign({}, this.state, { + sendFrom: this.props.ActiveCoin.activeAddress, + })); + } + } + + updateSocketsData(data) { + if (data && + data.message && + data.message.shepherd.iguanaAPI && + data.message.shepherd.iguanaAPI.totalStackLength) { + this.setState(Object.assign({}, this.state, { + totalStackLength: data.message.shepherd.iguanaAPI.totalStackLength, + })); + } + if (data && + data.message && + data.message.shepherd.iguanaAPI && + data.message.shepherd.iguanaAPI.currentStackLength) { + this.setState(Object.assign({}, this.state, { + currentStackLength: data.message.shepherd.iguanaAPI.currentStackLength, + })); + } + } + + _fetchNewUTXOData() { + Store.dispatch(fetchUtxoCache({ + pubkey: this.props.Dashboard.activeHandle.pubkey, + allcoins: false, + coin: this.props.ActiveCoin.coin, + calls: 'refresh', + address: this.state.sendFrom, + })); + } + + renderUTXOCacheInfo() { + if (this.props.ActiveCoin.mode === 'basilisk' && + this.state.sendFrom && + !this.state.sendApiType && + this.props.ActiveCoin.cache && + this.props.ActiveCoin.cache[this.props.ActiveCoin.coin][this.state.sendFrom]) { + let refreshCacheData; + let timestamp; + let isReadyToUpdate; + let waitUntilCallIsFinished = this.state.currentStackLength > 1 ? true : false; + const _cache = this.props.ActiveCoin.cache; + const _coin = this.props.ActiveCoin.coin; + const _sendFrom = this.state.sendFrom; + + if (_cache[_coin][_sendFrom].refresh || + _cache[_coin][_sendFrom].listunspent) { + refreshCacheData = _cache[_coin][_sendFrom].refresh || _cache[_coin][_sendFrom].listunspent; + timestamp = checkTimestamp(refreshCacheData.timestamp); + isReadyToUpdate = timestamp > 600 ? true : false; + } else { + isReadyToUpdate = true; + } + + if (_cache[_coin][_sendFrom].refresh && + _cache[_coin][_sendFrom].refresh.data && + _cache[_coin][_sendFrom].refresh.data.error && + _cache[_coin][_sendFrom].refresh.data.error === 'request failed') { + timestamp = null; + } + + return UTXOCacheInfoRender.call( + this, + refreshCacheData, + isReadyToUpdate, + waitUntilCallIsFinished, + timestamp + ); + } + + return null; + } + + renderAddressAmount(address) { + const _addresses = this.props.ActiveCoin.addresses; + + if (_addresses && + _addresses.public && + _addresses.public.length) { + for (let i = 0; i < _addresses.public.length; i++) { + if (_addresses.public[i].address === address) { + if (_addresses.public[i].amount !== 'N/A') { + return _addresses.public[i].amount; + } + } + } + } else { + return 0; + } + } + + renderAddressByType(type) { + const _addresses = this.props.ActiveCoin.addresses; + + if (_addresses && + _addresses[type] && + _addresses[type].length) { + if (this.state.sendApiType) { + const mainAddress = this.props.Dashboard.activeHandle[this.props.ActiveCoin.coin]; + const mainAddressAmount = this.renderAddressAmount(mainAddress); + + return( +
  • + this.updateAddressSelection(mainAddress, type, mainAddressAmount) }> +    + + [ { mainAddressAmount } { this.props.ActiveCoin.coin } ]   + { mainAddress } + + + +
  • + ); + } else { + let items = []; + const _addresses = this.props.ActiveCoin.addresses; + const _cache = this.props.ActiveCoin.cache; + const _coin = this.props.ActiveCoin.coin; + + for (let i = 0; i < _addresses[type].length; i++) { + const address = _addresses[type][i].address; + let _amount = address.amount; + + if (this.props.ActiveCoin.mode === 'basilisk' && + _cache) { + _amount = _cache[_coin][address] && _cache[_coin][address].getbalance.data && _cache[_coin][address].getbalance.data.balance ? _cache[_coin][address].getbalance.data.balance : 'N/A'; + } + + if (_amount !== 'N/A') { + items.push( +
  • + this.updateAddressSelection(address, type, _amount) }> +    + [ { _amount } { _coin } ]  { address } + + +
  • + ); + } + } + + return items; + } + } else { + return null; + } + } + + renderSelectorCurrentLabel() { + if (this.state.sendFrom) { + let _amount; + const _cache = this.props.ActiveCoin.cache; + const _coin = this.props.ActiveCoin.coin; + const _sendFrom = this.state.sendFrom; + + if (this.state.sendFromAmount === 0 && + this.props.ActiveCoin.mode === 'basilisk' && + _cache) { + _amount = _cache[_coin][_sendFrom].getbalance.data && _cache[_coin][_sendFrom].getbalance.data.balance ? _cache[_coin][_sendFrom].getbalance.data.balance : 'N/A'; + } else { + _amount = this.state.sendFromAmount; + } + + return ( + +    + [ { _amount } { _coin } ]  { _sendFrom } + + ); + } else if (this.state.sendApiType) { + const mainAddress = this.props.Dashboard.activeHandle[this.props.ActiveCoin.coin]; + const mainAddressAmount = this.renderAddressAmount(mainAddress); + + return ( + +    + [ { mainAddressAmount } { this.props.ActiveCoin.coin } ]  { mainAddress } + + ); + } else { + return ( + { translate('SEND.SELECT_T_OR_Z_ADDR') } + ); + } + } + + renderAddressList() { + return ( +
    + +
    + +
    +
    + ); + } + + openDropMenu() { + this.setState(Object.assign({}, this.state, { + addressSelectorOpen: !this.state.addressSelectorOpen, + })); + } + + updateAddressSelection(address, type, amount) { + let _sendFromAmount = amount ? amount : this.props.ActiveCoin.addresses[type][address].amount; + const _cache = this.props.ActiveCoin.cache; + const _coin = this.props.ActiveCoin.coin; + + if (this.props.ActiveCoin.mode === 'basilisk' && + this.props.ActiveCoin.cache) { + _sendFromAmount = _cache[_coin][address].getbalance.data && _cache[_coin][address].getbalance.data.balance ? _cache[_coin][address].getbalance.data.balance : 'N/A'; + } + + this.setState(Object.assign({}, this.state, { + sendFrom: address, + addressType: type, + sendFromAmount: _sendFromAmount, + addressSelectorOpen: !this.state.addressSelectorOpen, + })); + } + + changeSendCoinStep(step) { + if (step === 0) { + Store.dispatch(clearLastSendToResponseState()); + + this.setState({ + currentStep: 0, + sendFrom: this.props.Dashboard && this.props.Dashboard.activeHandle ? this.props.Dashboard.activeHandle[this.props.ActiveCoin.coin] : null, + sendFromAmount: 0, + sendTo: '', + sendToOA: null, + amount: 0, + fee: 0.0001, + sendSig: false, + sendApiType: true, + addressSelectorOpen: false, + currentStackLength: 0, + totalStackLength: 0, + utxoMethodInProgress: false, + }); + } + + if (step === 1) { + if (!this.validateSendFormData()) { + return; + } + } + + if (step === 1 || + step === 2) { + this.setState(Object.assign({}, this.state, { + currentStep: step, + utxoMethodInProgress: !this.state.sendApiType && this.props.ActiveCoin.mode === 'basilisk' ? true : false, + })); + } + + if (step === 2) { + if (!this.state.sendApiType && + this.props.ActiveCoin.mode === 'basilisk') { + this.handleBasiliskSend(); + } else { + Store.dispatch( + sendToAddress( + this.props.ActiveCoin.coin, + this.state + ) + ); + } + } + } + + toggleSendSig() { + this.setState(Object.assign({}, this.state, { + sendSig: !this.state.sendSig, + })); + } + + toggleSendAPIType() { + this.setState(Object.assign({}, this.state, { + sendApiType: !this.state.sendApiType, + fee: !this.state.sendApiType ? 0 : 0.0001, + sendFrom: this.props.Dashboard.activeHandle[this.props.ActiveCoin.coin], + })); + } + + updateInput(e) { + this.setState({ + [e.target.name]: e.target.value, + }); + } + + // TODO: move to action creators + handleBasiliskSend() { + const refreshData = this.props.ActiveCoin.cache[this.props.ActiveCoin.coin][this.state.sendFrom].refresh; + const listunspentData = this.props.ActiveCoin.cache[this.props.ActiveCoin.coin][this.state.sendFrom].listunspent; + const utxoSet = (refreshData && refreshData.data) || (listunspentData && listunspentData.data); + const _pubkey = this.props.Dashboard.activeHandle.pubkey; + const forceUpdateCache = this._fetchNewUTXOData; + const _sendFrom = this.state.sendFrom; + const sendData = { + coin: this.props.ActiveCoin.coin, + sendfrom: this.state.sendFrom, + sendtoaddr: this.state.sendTo, + amount: this.state.amount, + txfee: this.state.fee, + sendsig: this.state.sendSig === true ? 0 : 1, + utxos: utxoSet, + }; + + // TODO: es arrows + iguanaUTXORawTX(sendData, Store.dispatch) + .then(function(json) { + if (json.result === 'success' && + json.completed === true) { + Store.dispatch( + triggerToaster( + translate('TOASTR.SIGNED_TX_GENERATED'), + translate('TOASTR.WALLET_NOTIFICATION'), + 'success' + ) + ); + + if (sendData.sendsig === 1) { + const dexrawtxData = { + signedtx: json.signedtx, + coin: sendData.coin, + }; + dexSendRawTX( + dexrawtxData, + Store.dispatch + ).then(function(dexRawTxJSON) { + if (dexRawTxJSON.indexOf('"error":{"code"') > -1) { + Store.dispatch( + triggerToaster( + translate('TOASTR.TRANSACTION_FAILED'), + translate('TOASTR.WALLET_NOTIFICATION'), + 'error' + ) + ); + Store.dispatch(sendToAddressStateAlt(JSON.parse(dexRawTxJSON))); + + this.setState(Object.assign({}, this.state, { + utxoMethodInProgress: false, + })); + } else { + Store.dispatch( + triggerToaster( + translate('TOASTR.SIGNED_TX_SENT'), + translate('TOASTR.WALLET_NOTIFICATION'), + 'success' + ) + ); + Store.dispatch(sendToAddressStateAlt(json)); + + let getTxidData = function() { + return new Promise(function(resolve, reject) { + Store.dispatch( + triggerToaster( + translate('TOASTR.GETTING_TXID_INFO'), + translate('TOASTR.WALLET_NOTIFICATION'), + 'info' + ) + ); + + edexGetTransaction({ + coin: sendData.coin, + txid: dexRawTxJSON.txid ? dexRawTxJSON.txid : dexRawTxJSON, + }, Store.dispatch) + .then(function(json) { + resolve(json); + }); + }); + } + + let processRefreshUTXOs = function(vinData) { + return new Promise(function(resolve, reject) { + let edexGetTxIDListRes = edexGetTxIDList(vinData); + resolve(edexGetTxIDListRes); + }); + } + + let getDataCacheContents = function(txidListToRemove) { + return new Promise(function(resolve, reject) { + getCacheFile(_pubkey) + .then(function(result) { + let saveThisData = edexRemoveTXID(result.result, _sendFrom, txidListToRemove); + resolve(saveThisData); + }); + }); + } + + let saveNewCacheData = function(saveThisData) { + return new Promise(function(resolve, reject) { + shepherdGroomPostPromise( + _pubkey, + saveThisData + ).then(function(result) { + resolve(result); + forceUpdateCache(); + Store.dispatch( + triggerToaster( + translate('TOASTR.LOCAL_UTXO_UPDATED'), + translate('TOASTR.WALLET_NOTIFICATION'), + 'info' + ) + ); + + this.setState(Object.assign({}, this.state, { + utxoMethodInProgress: false, + })); + }.bind(this)); + }.bind(this)); + }.bind(this); + + Store.dispatch( + triggerToaster( + `${translate('TOASTR.AWAITING_TX_RESP')}...`, + translate('TOASTR.WALLET_NOTIFICATION'), + 'info' + ) + ); + + function waterfallUTXOProcess() { + Store.dispatch( + triggerToaster( + `${translate('TOASTR.PROCESSING_UTXO')}...`, + translate('TOASTR.WALLET_NOTIFICATION'), + 'info' + ) + ); + + getTxidData() + .then(function(gettxdata) { + return processRefreshUTXOs(gettxdata.vin); + }) + .then(function(new_utxos_set) { + return getDataCacheContents(new_utxos_set); + }) + .then(function(save_this_data) { + return saveNewCacheData(save_this_data); + }); + } + + let sentTxData = setInterval(function() { + getTxidData() + .then(function(gettxdata) { + if (gettxdata.vin && + gettxdata.vin.length) { + clearInterval(sentTxData); + waterfallUTXOProcess(); + } + }) + }, 1000); + } + }.bind(this)); + } else { + Store.dispatch(sendToAddressStateAlt(json)); + + this.setState(Object.assign({}, this.state, { + utxoMethodInProgress: false, + })); + } + } else { + Store.dispatch(sendToAddressStateAlt(json)); + Store.dispatch( + triggerToaster( + `${translate('TOASTR.SIGNED_TX_GENERATED_FAIL')}`, + translate('TOASTR.WALLET_NOTIFICATION'), + 'error' + ) + ); + + this.setState(Object.assign({}, this.state, { + utxoMethodInProgress: false, + })); + } + + // console.log(json); + }.bind(this)); + } + + renderSignedTx(isRawTx) { + let substrBlocks; + + if (this.props.ActiveCoin.mode === 'basilisk') { + substrBlocks = isRawTx ? 3 : 8; + } else { + substrBlocks = 10; + } + + const _lastSendToResponse = this.props.ActiveCoin.lastSendToResponse[isRawTx ? 'rawtx' : 'signedtx']; + const substrLength = _lastSendToResponse.length / substrBlocks; + let out = []; + + for (let i = 0; i < substrBlocks; i++) { + out.push( +
    { _lastSendToResponse.substring(i * substrLength, substrLength * i + substrLength) }
    + ); + } + + return out.length ? out : null; + } + + renderKey(key) { + if (key === 'signedtx') { + return this.renderSignedTx(); + } else if (key === 'rawtx') { + return this.renderSignedTx(true); + } else if (key === 'complete' || key === 'completed' || key === 'result') { + const _lastSendToResponse = this.props.ActiveCoin.lastSendToResponse; + + if (_lastSendToResponse[key] === true || + _lastSendToResponse[key] === 'success') { + return ( + { _lastSendToResponse[key] === true ? 'true' : 'success' } + ); + } else { + if (key === 'result' && + _lastSendToResponse.result && + typeof _lastSendToResponse.result !== 'object') { + return ( + { _lastSendToResponse.result } + ); + } else { + return ( + false + ); + } + } + } else if (key === 'error') { + const _lastSendToResponse = this.props.ActiveCoin.lastSendToResponse; + + if (Object.keys(_lastSendToResponse[key]).length) { + return ( + { JSON.stringify(_lastSendToResponse[key], null, '\t') } + ); + } else { + return ( + { _lastSendToResponse[key] } + ); + } + } else if (key === 'sendrawtransaction') { + const _lastSendToResponse = this.props.ActiveCoin.lastSendToResponse; + + if (_lastSendToResponse[key] === 'success') { + return ( + true + ); + } else { + return ( + false + ); + } + } else if (key === 'txid' || key === 'sent') { + const _lastSendToResponse = this.props.ActiveCoin.lastSendToResponse; + + return ( + { _lastSendToResponse[key] } + ); + } else if (key === 'tag') { + return null; + } + } + + renderSendCoinResponse() { + return SendCoinResponseRender.call(this); + } + + // experimental, ask @kolo for details if required + getOAdress() { + resolveOpenAliasAddress(this.state.sendToOA) + .then(function(json) { + const reply = json.Answer; + + if (reply && + reply.length) { + for (let i = 0; i < reply.length; i++) { + const _address = reply[i].data.split(' '); + const coin = _address[0].replace('"oa1:', ''); + const coinAddress = _address[1].replace('recipient_address=', '').replace(';', ''); + + if (coin.toUpperCase() === this.props.ActiveCoin.coin) { + this.setState(Object.assign({}, this.state, { + sendTo: coinAddress, + })); + } + } + + if (this.state.sendTo === '') { + Store.dispatch( + triggerToaster( + 'Couldn\'t find any ' + this.props.ActiveCoin.coin + ' addresses', + 'OpenAlias', + 'error' + ) + ); + } + } else { + Store.dispatch( + triggerToaster( + 'Couldn\'t find any addresses', + 'OpenAlias', + 'error' + ) + ); + } + }.bind(this)); + } + + renderOASendUI() { + if (Config.openAlias) { + return OASendUIRender.call(this); + } + + return null; + } + + renderSendApiTypeSelector() { + if (this.props.ActiveCoin.mode === 'basilisk') { + return SendApiTypeSelectorRender.call(this); + } + + return null; + } + + // TODO same as in walletsNav and receiveCoin, find a way to reuse it? + checkBalance() { + let _balance = '0'; + const _mode = this.props.ActiveCoin.mode; + + if (_mode === 'full') { + _balance = this.props.ActiveCoin.balance || 0; + } else if (_mode === 'basilisk') { + if (this.props.ActiveCoin.cache) { + const _cache = this.props.ActiveCoin.cache; + const _coin = this.props.ActiveCoin.coin; + const _address = this.props.ActiveCoin.activeAddress; + + if (_address && + _cache[_coin] && + _cache[_coin][_address] && + _cache[_coin][_address].getbalance && + _cache[_coin][_address].getbalance.data && + (_cache[_coin][_address].getbalance.data.balance || + _cache[_coin][_address].getbalance.data.interest)) { + const _regBalance = _cache[_coin][_address].getbalance.data.balance ? _cache[_coin][_address].getbalance.data.balance : 0; + + _balance = _regBalance; + } + } + } + + return _balance; + } + + // TODO: reduce to a single toast + validateSendFormData() { + let valid = true; + if (!this.state.sendTo || this.state.sendTo.length < 34) { + Store.dispatch( + triggerToaster( + translate('SEND.SEND_TO_ADDRESS_MIN_LENGTH'), + '', + 'error' + ) + ); + valid = false; + } + + if (!isPositiveNumber(this.state.amount)) { + Store.dispatch( + triggerToaster( + translate('SEND.AMOUNT_POSITIVE_NUMBER'), + '', + 'error' + ) + ); + valid = false; + } + + if (!isPositiveNumber(this.state.fee)) { + Store.dispatch( + triggerToaster( + translate('SEND.FEE_POSITIVE_NUMBER'), + '', + 'error' + ) + ); + valid = false; + } + + if (!isPositiveNumber(this.getTotalAmount())) { + Store.dispatch( + triggerToaster( + translate('SEND.TOTAL_AMOUNT_POSITIVE_NUMBER'), + '', + 'error' + ) + ); + valid = false; + } + + /*if ((this.props.ActiveCoin.mode === 'basilisk' && Number(this.state.amount) > Number(this.state.sendFromAmount)) || + (this.props.ActiveCoin.mode === 'full' && Number(this.state.amount) > Number(this.checkBalance()))) { + Store.dispatch( + triggerToaster( + translate('SEND.INSUFFICIENT_FUNDS'), + '', + 'error' + ) + ); + valid = false; + }*/ + + return valid; + } + + getTotalAmount() { + return Number(this.state.amount) - Number(this.state.fee); + } + + render() { + if (this.props.ActiveCoin && + this.props.ActiveCoin.send && + this.props.ActiveCoin.mode !== 'native') { + return SendCoinRender.call(this); + } + + return null; + } +} + +const mapStateToProps = (state) => { + return { + ActiveCoin: { + coin: state.ActiveCoin.coin, + mode: state.ActiveCoin.mode, + send: state.ActiveCoin.send, + receive: state.ActiveCoin.receive, + balance: state.ActiveCoin.balance, + cache: state.ActiveCoin.cache, + activeAddress: state.ActiveCoin.activeAddress, + lastSendToResponse: state.ActiveCoin.lastSendToResponse, + addresses: state.ActiveCoin.addresses, + }, + Dashboard: { + activeHandle: state.Dashboard.activeHandle, + }, + }; +}; + +export default connect(mapStateToProps)(SendCoin); \ No newline at end of file diff --git a/react/src/components/dashboard/_sendCoin/sendCoin.render.js b/react/src/components/dashboard/_sendCoin/sendCoin.render.js new file mode 100644 index 0000000..4ca3d8d --- /dev/null +++ b/react/src/components/dashboard/_sendCoin/sendCoin.render.js @@ -0,0 +1,402 @@ +import React from 'react'; +import { translate } from '../../../translate/translate'; +import { + secondsElapsedToString, + secondsToString +} from '../../../util/time'; + +import QRModal from '../qrModal/qrModal'; + +export const UTXOCacheInfoRender = function(refreshCacheData, isReadyToUpdate, waitUntilCallIsFinished, timestamp) { + const _progress = 100 - this.state.currentStackLength * 100 / this.state.totalStackLength; + + return ( +
    +
    + { translate('SEND.TOTAL_UTXO_AVAILABLE') }: + { refreshCacheData ? refreshCacheData.data && refreshCacheData.data.length : translate('SEND.PRESS_UPDATE_BTN') }
    +
    + { translate('SEND.LAST_UPDATED') } @ + { secondsToString(refreshCacheData ? refreshCacheData.timestamp : 0, true) } |  + { secondsElapsedToString(timestamp || 0) }&nbps; + { translate('SEND.AGO') }
    +
    +
    + { translate('SEND.NEXT_UPDATE_IN') } { secondsElapsedToString(600 - timestamp) }s +
    +
    +
    + { translate('SEND.PROCESSING_REQ') }: { this.state.currentStackLength } / { this.state.totalStackLength } +
    +
    + +
    + ); +}; + +export const SendCoinResponseRender = function() { + if (this.props.ActiveCoin.lastSendToResponse) { + let items = []; + const _response = this.props.ActiveCoin.lastSendToResponse; + + for (let key in _response) { + if (key !== 'tag') { + items.push( + + { key } + { this.renderKey(key) } + + ); + } + } + + return items; + } else { + return ( + + +
    +
    + { translate('SEND.PROCESSING_TRANSACTION') }...
    + { translate('SEND.NOTE_IT_WILL_TAKE') }. +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + ); + } +} + +export const OASendUIRender = function() { + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; + +export const SendApiTypeSelectorRender = function() { + return ( +
    +
    + + +
    + { translate('SEND.SEND_VIA') } (sendtoaddress API) +
    +
    +
    +
    + +
    +
    + ); +}; + +export const SendCoinRender = function() { + return ( +
    +
    +
    +
    + 1 +
    + { translate('INDEX.FILL_SEND_FORM') } +

    { translate('INDEX.FILL_SEND_DETAILS') }

    +
    +
    +
    + 2 +
    + { translate('INDEX.CONFIRMING') } +

    { translate('INDEX.CONFIRM_DETAILS') }

    +
    +
    +
    + 3 +
    + { translate('INDEX.PROCESSING_TX') } +

    { translate('INDEX.PROCESSING_DETAILS') }

    +
    +
    +
    + +
    +
    +

    + { translate('INDEX.SEND') } { this.props.ActiveCoin.coin } +

    +
    +
    +
    + { this.renderSendApiTypeSelector() } +
    +
    + + { this.renderAddressList() } +
    +
    + { this.renderOASendUI() } +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + { translate('INDEX.TOTAL') }  + ({ translate('INDEX.AMOUNT_SM') } - { translate('INDEX.FEE') }): +   + { this.getTotalAmount() } { this.props.ActiveCoin.coin } +
    +
    + + +
    + { translate('INDEX.DONT_SEND') } +
    +
    +
    + { this.renderUTXOCacheInfo()} +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + { translate('INDEX.TO') } +
    +
    { this.state.sendTo }
    +
    + { this.state.amount } { this.props.ActiveCoin.coin } +
    +
    { translate('INDEX.TX_FEE_REQ') }
    +
    + { this.state.fee } { this.props.ActiveCoin.coin } +
    +
    +
    + +
    +
    + { translate('INDEX.FROM') } +
    +
    + { this.props.Dashboard.activeHandle[this.props.ActiveCoin.coin] } +
    +
    + { Number(this.state.amount) - Number(this.state.fee) } { this.props.ActiveCoin.coin } +
    +
    +
    + this.changeSendCoinStep(0) }>{ translate('INDEX.BACK') } +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +

    + { translate('INDEX.TRANSACTION_RESULT') } +

    +
    + { translate('SEND.YOU_PICKED_OPT') } +
    + + + + + + + + + { this.renderSendCoinResponse() } + +
    { translate('INDEX.KEY') }{ translate('INDEX.INFO') }
    +
    +
    + +
    +
    +
    +
    +
    +
    + ); +}; \ No newline at end of file diff --git a/react/src/components/dashboard/jumblr/jumblr.render.js b/react/src/components/dashboard/jumblr/jumblr.render.js index 0a988cd..f2c919d 100644 --- a/react/src/components/dashboard/jumblr/jumblr.render.js +++ b/react/src/components/dashboard/jumblr/jumblr.render.js @@ -2,7 +2,7 @@ import React from 'react'; import { translate } from '../../../translate/translate'; import WalletsHeader from '../walletsHeader/walletsHeader'; -import WalletsNativeSend from '../walletsNativeSend/walletsNativeSend'; +import SendCoin from '../sendCoin/sendCoin'; import ReceiveCoin from '../receiveCoin/receiveCoin'; export const JumblrRenderSecretAddressList = function(type) { @@ -338,7 +338,7 @@ export const JumblrRender = function() {

    { translate('JUMBLR.DEPOSIT_FORM_P1') }

    { translate('JUMBLR.DEPOSIT_FORM_P2') }

    -
    diff --git a/react/src/components/dashboard/sendCoin/sendCoin.js b/react/src/components/dashboard/sendCoin/sendCoin.js index c97cd2b..135454a 100644 --- a/react/src/components/dashboard/sendCoin/sendCoin.js +++ b/react/src/components/dashboard/sendCoin/sendCoin.js @@ -2,68 +2,82 @@ import React from 'react'; import { connect } from 'react-redux'; import Config from '../../../config'; import { translate } from '../../../translate/translate'; -import { checkTimestamp } from '../../../util/time'; +import { secondsToString } from '../../../util/time'; import { - edexGetTxIDList, - edexRemoveTXID -} from '../../../util/cacheFormat'; -import { - resolveOpenAliasAddress, triggerToaster, - shepherdGroomPostPromise, - edexGetTransaction, - getCacheFile, - fetchUtxoCache, - sendToAddress, - iguanaUTXORawTX, - clearLastSendToResponseState, - sendToAddressStateAlt, - dexSendRawTX + sendNativeTx, + getKMDOPID, + clearLastSendToResponseState } from '../../../actions/actionCreators'; import Store from '../../../store'; import { - UTXOCacheInfoRender, - SendCoinResponseRender, - OASendUIRender, - SendApiTypeSelectorRender, - SendCoinRender + AddressListRender, + SendRender, + SendFormRender, + _SendFormRender } from './sendCoin.render'; - -import io from 'socket.io-client'; import { isPositiveNumber } from '../../../util/number'; -const socket = io.connect(`http://127.0.0.1:${Config.agamaPort}`); -// TODO: prevent any cache updates rather than utxo while on send coin form -// fix a bug - total amount is incorrect when switching between steps +// TODO: - add links to explorers +// - render z address trim class SendCoin extends React.Component { constructor(props) { super(props); this.state = { currentStep: 0, - sendFrom: this.props.Dashboard.activeHandle ? this.props.Dashboard.activeHandle[this.props.ActiveCoin.coin] : null, + addressType: null, + sendFrom: null, sendFromAmount: 0, sendTo: '', - sendToOA: null, amount: 0, - fee: 0.0001, - sendSig: false, - sendApiType: true, + fee: 0, addressSelectorOpen: false, - currentStackLength: 0, - totalStackLength: 0, - utxoMethodInProgress: false, + renderAddressDropdown: true, + substractFee: false, + lastSendToResponse: null, + coin: null, }; this.updateInput = this.updateInput.bind(this); - this.handleBasiliskSend = this.handleBasiliskSend.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); this.openDropMenu = this.openDropMenu.bind(this); - this.toggleSendSig = this.toggleSendSig.bind(this); - this.getOAdress = this.getOAdress.bind(this); - this.toggleSendAPIType = this.toggleSendAPIType.bind(this); - this._fetchNewUTXOData = this._fetchNewUTXOData.bind(this); this.handleClickOutside = this.handleClickOutside.bind(this); + this.checkZAddressCount = this.checkZAddressCount.bind(this); this.setRecieverFromScan = this.setRecieverFromScan.bind(this); - socket.on('messages', msg => this.updateSocketsData(msg)); + this.renderOPIDListCheck = this.renderOPIDListCheck.bind(this); + this.SendFormRender = _SendFormRender.bind(this); + this.isTransparentTx = this.isTransparentTx.bind(this); + this.toggleSubstractFee = this.toggleSubstractFee.bind(this); + } + + SendFormRender() { + return _SendFormRender.call(this); + } + + toggleSubstractFee() { + this.setState({ + substractFee: !this.state.substractFee, + }); + } + + componentWillMount() { + document.addEventListener( + 'click', + this.handleClickOutside, + false + ); + } + + componentWillUnmount() { + document.removeEventListener( + 'click', + this.handleClickOutside, + false + ); + } + + componentWillReceiveProps(props) { + this.checkZAddressCount(props); } setRecieverFromScan(receiver) { @@ -86,10 +100,10 @@ class SendCoin extends React.Component { } else { Store.dispatch( triggerToaster( - translate('SEND.QR_COIN_MISMATCH_MESSAGE_IMPORT_COIN') + - recObj.coin + - translate('SEND.QR_COIN_MISMATCH_MESSAGE_ACTIVE_COIN') + - this.props.ActiveCoin.coin + + translate('SEND.QR_COIN_MISMATCH_MESSAGE_IMPORT_COIN') + + recObj.coin + + translate('SEND.QR_COIN_MISMATCH_MESSAGE_ACTIVE_COIN') + + this.props.ActiveCoin.coin + translate('SEND.QR_COIN_MISMATCH_MESSAGE_END'), translate('SEND.QR_COIN_MISMATCH_TITLE'), 'warning' @@ -103,23 +117,7 @@ class SendCoin extends React.Component { }); } - document.getElementById('edexcoinSendTo').focus(); - } - - componentWillMount() { - document.addEventListener( - 'click', - this.handleClickOutside, - false - ); - } - - componentWillUnmount() { - document.removeEventListener( - 'click', - this.handleClickOutside, - false - ); + document.getElementById('kmdWalletSendTo').focus(); } handleClickOutside(e) { @@ -132,232 +130,193 @@ class SendCoin extends React.Component { } } - componentWillReceiveProps(props) { - if (this.state && - !this.state.sendFrom && - this.props.ActiveCoin.activeAddress) { - this.setState(Object.assign({}, this.state, { - sendFrom: this.props.ActiveCoin.activeAddress, - })); - } - } + checkZAddressCount(props) { + const _addresses = this.props.ActiveCoin.addresses; + const _defaultState = { + currentStep: 0, + addressType: null, + sendFrom: null, + sendFromAmount: 0, + sendTo: '', + amount: 0, + fee: 0, + addressSelectorOpen: false, + renderAddressDropdown: true, + substractFee: false, + lastSendToResponse: null, + }; + let updatedState; - updateSocketsData(data) { - if (data && - data.message && - data.message.shepherd.iguanaAPI && - data.message.shepherd.iguanaAPI.totalStackLength) { - this.setState(Object.assign({}, this.state, { - totalStackLength: data.message.shepherd.iguanaAPI.totalStackLength, - })); - } - if (data && - data.message && - data.message.shepherd.iguanaAPI && - data.message.shepherd.iguanaAPI.currentStackLength) { - this.setState(Object.assign({}, this.state, { - currentStackLength: data.message.shepherd.iguanaAPI.currentStackLength, - })); + if (_addresses && + (!_addresses.private || + _addresses.private.length === 0)) { + updatedState = { + renderAddressDropdown: false, + lastSendToResponse: props.ActiveCoin.lastSendToResponse, + coin: props.ActiveCoin.coin, + }; + } else { + updatedState = { + renderAddressDropdown: true, + lastSendToResponse: props.ActiveCoin.lastSendToResponse, + coin: props.ActiveCoin.coin, + }; } - } - _fetchNewUTXOData() { - Store.dispatch(fetchUtxoCache({ - pubkey: this.props.Dashboard.activeHandle.pubkey, - allcoins: false, - coin: this.props.ActiveCoin.coin, - calls: 'refresh', - address: this.state.sendFrom, - })); + if (this.state.coin !== props.ActiveCoin.coin) { + this.setState(Object.assign({}, _defaultState, updatedState)); + } else { + this.setState(updatedState); + } } - renderUTXOCacheInfo() { - if (this.props.ActiveCoin.mode === 'basilisk' && - this.state.sendFrom && - !this.state.sendApiType && - this.props.ActiveCoin.cache && - this.props.ActiveCoin.cache[this.props.ActiveCoin.coin][this.state.sendFrom]) { - let refreshCacheData; - let timestamp; - let isReadyToUpdate; - let waitUntilCallIsFinished = this.state.currentStackLength > 1 ? true : false; - const _cache = this.props.ActiveCoin.cache; - const _coin = this.props.ActiveCoin.coin; - const _sendFrom = this.state.sendFrom; - - if (_cache[_coin][_sendFrom].refresh || - _cache[_coin][_sendFrom].listunspent) { - refreshCacheData = _cache[_coin][_sendFrom].refresh || _cache[_coin][_sendFrom].listunspent; - timestamp = checkTimestamp(refreshCacheData.timestamp); - isReadyToUpdate = timestamp > 600 ? true : false; - } else { - isReadyToUpdate = true; - } - - if (_cache[_coin][_sendFrom].refresh && - _cache[_coin][_sendFrom].refresh.data && - _cache[_coin][_sendFrom].refresh.data.error && - _cache[_coin][_sendFrom].refresh.data.error === 'request failed') { - timestamp = null; - } + renderAddressByType(type) { + let _items = []; + + if (this.props.ActiveCoin.addresses && + this.props.ActiveCoin.addresses[type] && + this.props.ActiveCoin.addresses[type].length) { + this.props.ActiveCoin.addresses[type].map((address) => { + if (address.amount > 0) { + _items.push( +
  • + this.updateAddressSelection(address.address, type, address.amount) }> +    + + [ { address.amount } { this.props.ActiveCoin.coin } ]   + { type === 'public' ? address.address : address.address.substring(0, 34) + '...' } + + + +
  • + ); + } + }); - return UTXOCacheInfoRender.call( - this, - refreshCacheData, - isReadyToUpdate, - waitUntilCallIsFinished, - timestamp - ); + return _items; + } else { + return null; } - - return null; } - renderAddressAmount(address) { - const _addresses = this.props.ActiveCoin.addresses; + renderOPIDListCheck() { + if (this.state.renderAddressDropdown && + this.props.ActiveCoin.opids && + this.props.ActiveCoin.opids.length) { + return true; + } + } - if (_addresses && - _addresses.public && - _addresses.public.length) { - for (let i = 0; i < _addresses.public.length; i++) { - if (_addresses.public[i].address === address) { - if (_addresses.public[i].amount !== 'N/A') { - return _addresses.public[i].amount; - } - } - } + renderSelectorCurrentLabel() { + if (this.state.sendFrom) { + return ( + + + + [ { this.state.sendFromAmount } { this.props.ActiveCoin.coin } ]   + { this.state.addressType === 'public' ? this.state.sendFrom : this.state.sendFrom.substring(0, 34) + '...' } + + + ); } else { - return 0; + return ( + { translate('INDEX.T_FUNDS') } + ); } } - renderAddressByType(type) { - const _addresses = this.props.ActiveCoin.addresses; - - if (_addresses && - _addresses[type] && - _addresses[type].length) { - if (this.state.sendApiType) { - const mainAddress = this.props.Dashboard.activeHandle[this.props.ActiveCoin.coin]; - const mainAddressAmount = this.renderAddressAmount(mainAddress); - - return( -
  • - this.updateAddressSelection(mainAddress, type, mainAddressAmount) }> -    - - [ { mainAddressAmount } { this.props.ActiveCoin.coin } ]   - { mainAddress } - - - -
  • - ); - } else { - let items = []; - const _addresses = this.props.ActiveCoin.addresses; - const _cache = this.props.ActiveCoin.cache; - const _coin = this.props.ActiveCoin.coin; - - for (let i = 0; i < _addresses[type].length; i++) { - const address = _addresses[type][i].address; - let _amount = address.amount; - - if (this.props.ActiveCoin.mode === 'basilisk' && - _cache) { - _amount = _cache[_coin][address] && _cache[_coin][address].getbalance.data && _cache[_coin][address].getbalance.data.balance ? _cache[_coin][address].getbalance.data.balance : 'N/A'; - } + renderAddressList() { + return AddressListRender.call(this); + } - if (_amount !== 'N/A') { - items.push( -
  • - this.updateAddressSelection(address, type, _amount) }> -    - [ { _amount } { _coin } ]  { address } - - -
  • - ); - } - } + renderOPIDLabel(opid) { + const _satatusDef = { + queued: { + icon: 'warning', + label: 'QUEUED', + }, + executing: { + icon: 'info', + label: 'EXECUTING', + }, + failed: { + icon: 'danger', + label: 'FAILED', + }, + success: { + icon: 'success', + label: 'SUCCESS', + }, + }; - return items; - } - } else { - return null; - } + return ( + +   + { translate(`KMD_NATIVE.${_satatusDef[opid.status].label}`) } + + ); } - renderSelectorCurrentLabel() { - if (this.state.sendFrom) { - let _amount; - const _cache = this.props.ActiveCoin.cache; - const _coin = this.props.ActiveCoin.coin; - const _sendFrom = this.state.sendFrom; - - if (this.state.sendFromAmount === 0 && - this.props.ActiveCoin.mode === 'basilisk' && - _cache) { - _amount = _cache[_coin][_sendFrom].getbalance.data && _cache[_coin][_sendFrom].getbalance.data.balance ? _cache[_coin][_sendFrom].getbalance.data.balance : 'N/A'; - } else { - _amount = this.state.sendFromAmount; - } + renderOPIDResult(opid) { + let isWaitingStatus = true; + if (opid.status === 'queued') { + isWaitingStatus = false; + return ( + { translate('SEND.AWAITING') }... + ); + } + if (opid.status === 'executing') { + isWaitingStatus = false; + return ( + { translate('SEND.PROCESSING') }... + ); + } + if (opid.status === 'failed') { + isWaitingStatus = false; return ( -    - [ { _amount } { _coin } ]  { _sendFrom } + { translate('SEND.ERROR_CODE') }: { opid.error.code } +
    + { translate('KMD_NATIVE.MESSAGE') }: { opid.error.message }
    ); - } else if (this.state.sendApiType) { - const mainAddress = this.props.Dashboard.activeHandle[this.props.ActiveCoin.coin]; - const mainAddressAmount = this.renderAddressAmount(mainAddress); - + } + if (opid.status === 'success') { + isWaitingStatus = false; return ( -    - [ { mainAddressAmount } { this.props.ActiveCoin.coin } ]  { mainAddress } + { translate('KMD_NATIVE.TXID') }: { opid.result.txid } +
    + { translate('KMD_NATIVE.EXECUTION_SECONDS') }: { opid.execution_secs }
    ); - } else { + } + if (isWaitingStatus) { return ( - { translate('SEND.SELECT_T_OR_Z_ADDR') } + { translate('SEND.WAITING') }... ); } } - renderAddressList() { - return ( -
    - -
    - -
    -
    - ); + renderOPIDList() { + if (this.props.ActiveCoin.opids && + this.props.ActiveCoin.opids.length) { + return this.props.ActiveCoin.opids.map((opid) => + + { this.renderOPIDLabel(opid) } + { opid.id } + { secondsToString(opid.creation_time) } + { this.renderOPIDResult(opid) } + + ); + } else { + return null; + } } openDropMenu() { @@ -367,42 +326,43 @@ class SendCoin extends React.Component { } updateAddressSelection(address, type, amount) { - let _sendFromAmount = amount ? amount : this.props.ActiveCoin.addresses[type][address].amount; - const _cache = this.props.ActiveCoin.cache; - const _coin = this.props.ActiveCoin.coin; - - if (this.props.ActiveCoin.mode === 'basilisk' && - this.props.ActiveCoin.cache) { - _sendFromAmount = _cache[_coin][address].getbalance.data && _cache[_coin][address].getbalance.data.balance ? _cache[_coin][address].getbalance.data.balance : 'N/A'; - } - this.setState(Object.assign({}, this.state, { sendFrom: address, addressType: type, - sendFromAmount: _sendFromAmount, + sendFromAmount: amount, addressSelectorOpen: !this.state.addressSelectorOpen, })); } - changeSendCoinStep(step) { - if (step === 0) { - Store.dispatch(clearLastSendToResponseState()); + updateInput(e) { + this.setState({ + [e.target.name]: e.target.value, + }); + } - this.setState({ - currentStep: 0, - sendFrom: this.props.Dashboard && this.props.Dashboard.activeHandle ? this.props.Dashboard.activeHandle[this.props.ActiveCoin.coin] : null, - sendFromAmount: 0, - sendTo: '', - sendToOA: null, - amount: 0, - fee: 0.0001, - sendSig: false, - sendApiType: true, - addressSelectorOpen: false, - currentStackLength: 0, - totalStackLength: 0, - utxoMethodInProgress: false, - }); + changeSendCoinStep(step, back) { + if (step === 0) { + if (back) { + this.setState({ + currentStep: 0, + }); + } else { + Store.dispatch(clearLastSendToResponseState()); + + this.setState({ + currentStep: 0, + addressType: null, + sendFrom: null, + sendFromAmount: 0, + sendTo: '', + sendToOA: null, + amount: 0, + fee: 0, + addressSelectorOpen: false, + renderAddressDropdown: true, + substractFee: false, + }); + } } if (step === 1) { @@ -415,417 +375,48 @@ class SendCoin extends React.Component { step === 2) { this.setState(Object.assign({}, this.state, { currentStep: step, - utxoMethodInProgress: !this.state.sendApiType && this.props.ActiveCoin.mode === 'basilisk' ? true : false, })); } if (step === 2) { - if (!this.state.sendApiType && - this.props.ActiveCoin.mode === 'basilisk') { - this.handleBasiliskSend(); - } else { - Store.dispatch( - sendToAddress( - this.props.ActiveCoin.coin, - this.state - ) - ); - } + this.handleSubmit(); } } - toggleSendSig() { - this.setState(Object.assign({}, this.state, { - sendSig: !this.state.sendSig, - })); - } - - toggleSendAPIType() { - this.setState(Object.assign({}, this.state, { - sendApiType: !this.state.sendApiType, - fee: !this.state.sendApiType ? 0 : 0.0001, - sendFrom: this.props.Dashboard.activeHandle[this.props.ActiveCoin.coin], - })); - } - - updateInput(e) { - this.setState({ - [e.target.name]: e.target.value, - }); - } - - // TODO: move to action creators - handleBasiliskSend() { - const refreshData = this.props.ActiveCoin.cache[this.props.ActiveCoin.coin][this.state.sendFrom].refresh; - const listunspentData = this.props.ActiveCoin.cache[this.props.ActiveCoin.coin][this.state.sendFrom].listunspent; - const utxoSet = (refreshData && refreshData.data) || (listunspentData && listunspentData.data); - const _pubkey = this.props.Dashboard.activeHandle.pubkey; - const forceUpdateCache = this._fetchNewUTXOData; - const _sendFrom = this.state.sendFrom; - const sendData = { - coin: this.props.ActiveCoin.coin, - sendfrom: this.state.sendFrom, - sendtoaddr: this.state.sendTo, - amount: this.state.amount, - txfee: this.state.fee, - sendsig: this.state.sendSig === true ? 0 : 1, - utxos: utxoSet, - }; - - // TODO: es arrows - iguanaUTXORawTX(sendData, Store.dispatch) - .then(function(json) { - if (json.result === 'success' && - json.completed === true) { - Store.dispatch( - triggerToaster( - translate('TOASTR.SIGNED_TX_GENERATED'), - translate('TOASTR.WALLET_NOTIFICATION'), - 'success' - ) - ); - - if (sendData.sendsig === 1) { - const dexrawtxData = { - signedtx: json.signedtx, - coin: sendData.coin, - }; - dexSendRawTX( - dexrawtxData, - Store.dispatch - ).then(function(dexRawTxJSON) { - if (dexRawTxJSON.indexOf('"error":{"code"') > -1) { - Store.dispatch( - triggerToaster( - translate('TOASTR.TRANSACTION_FAILED'), - translate('TOASTR.WALLET_NOTIFICATION'), - 'error' - ) - ); - Store.dispatch(sendToAddressStateAlt(JSON.parse(dexRawTxJSON))); - - this.setState(Object.assign({}, this.state, { - utxoMethodInProgress: false, - })); - } else { - Store.dispatch( - triggerToaster( - translate('TOASTR.SIGNED_TX_SENT'), - translate('TOASTR.WALLET_NOTIFICATION'), - 'success' - ) - ); - Store.dispatch(sendToAddressStateAlt(json)); - - let getTxidData = function() { - return new Promise(function(resolve, reject) { - Store.dispatch( - triggerToaster( - translate('TOASTR.GETTING_TXID_INFO'), - translate('TOASTR.WALLET_NOTIFICATION'), - 'info' - ) - ); - - edexGetTransaction({ - coin: sendData.coin, - txid: dexRawTxJSON.txid ? dexRawTxJSON.txid : dexRawTxJSON, - }, Store.dispatch) - .then(function(json) { - resolve(json); - }); - }); - } - - let processRefreshUTXOs = function(vinData) { - return new Promise(function(resolve, reject) { - let edexGetTxIDListRes = edexGetTxIDList(vinData); - resolve(edexGetTxIDListRes); - }); - } - - let getDataCacheContents = function(txidListToRemove) { - return new Promise(function(resolve, reject) { - getCacheFile(_pubkey) - .then(function(result) { - let saveThisData = edexRemoveTXID(result.result, _sendFrom, txidListToRemove); - resolve(saveThisData); - }); - }); - } - - let saveNewCacheData = function(saveThisData) { - return new Promise(function(resolve, reject) { - shepherdGroomPostPromise( - _pubkey, - saveThisData - ).then(function(result) { - resolve(result); - forceUpdateCache(); - Store.dispatch( - triggerToaster( - translate('TOASTR.LOCAL_UTXO_UPDATED'), - translate('TOASTR.WALLET_NOTIFICATION'), - 'info' - ) - ); - - this.setState(Object.assign({}, this.state, { - utxoMethodInProgress: false, - })); - }.bind(this)); - }.bind(this)); - }.bind(this); - - Store.dispatch( - triggerToaster( - `${translate('TOASTR.AWAITING_TX_RESP')}...`, - translate('TOASTR.WALLET_NOTIFICATION'), - 'info' - ) - ); - - function waterfallUTXOProcess() { - Store.dispatch( - triggerToaster( - `${translate('TOASTR.PROCESSING_UTXO')}...`, - translate('TOASTR.WALLET_NOTIFICATION'), - 'info' - ) - ); - - getTxidData() - .then(function(gettxdata) { - return processRefreshUTXOs(gettxdata.vin); - }) - .then(function(new_utxos_set) { - return getDataCacheContents(new_utxos_set); - }) - .then(function(save_this_data) { - return saveNewCacheData(save_this_data); - }); - } - - let sentTxData = setInterval(function() { - getTxidData() - .then(function(gettxdata) { - if (gettxdata.vin && - gettxdata.vin.length) { - clearInterval(sentTxData); - waterfallUTXOProcess(); - } - }) - }, 1000); - } - }.bind(this)); - } else { - Store.dispatch(sendToAddressStateAlt(json)); - - this.setState(Object.assign({}, this.state, { - utxoMethodInProgress: false, - })); - } - } else { - Store.dispatch(sendToAddressStateAlt(json)); - Store.dispatch( - triggerToaster( - `${translate('TOASTR.SIGNED_TX_GENERATED_FAIL')}`, - translate('TOASTR.WALLET_NOTIFICATION'), - 'error' - ) - ); - - this.setState(Object.assign({}, this.state, { - utxoMethodInProgress: false, - })); - } - - // console.log(json); - }.bind(this)); - } - - renderSignedTx(isRawTx) { - let substrBlocks; - - if (this.props.ActiveCoin.mode === 'basilisk') { - substrBlocks = isRawTx ? 3 : 8; - } else { - substrBlocks = 10; - } - - const _lastSendToResponse = this.props.ActiveCoin.lastSendToResponse[isRawTx ? 'rawtx' : 'signedtx']; - const substrLength = _lastSendToResponse.length / substrBlocks; - let out = []; - - for (let i = 0; i < substrBlocks; i++) { - out.push( -
    { _lastSendToResponse.substring(i * substrLength, substrLength * i + substrLength) }
    - ); - } - - return out.length ? out : null; - } - - renderKey(key) { - if (key === 'signedtx') { - return this.renderSignedTx(); - } else if (key === 'rawtx') { - return this.renderSignedTx(true); - } else if (key === 'complete' || key === 'completed' || key === 'result') { - const _lastSendToResponse = this.props.ActiveCoin.lastSendToResponse; - - if (_lastSendToResponse[key] === true || - _lastSendToResponse[key] === 'success') { - return ( - { _lastSendToResponse[key] === true ? 'true' : 'success' } - ); - } else { - if (key === 'result' && - _lastSendToResponse.result && - typeof _lastSendToResponse.result !== 'object') { - return ( - { _lastSendToResponse.result } - ); - } else { - return ( - false - ); - } - } - } else if (key === 'error') { - const _lastSendToResponse = this.props.ActiveCoin.lastSendToResponse; - - if (Object.keys(_lastSendToResponse[key]).length) { - return ( - { JSON.stringify(_lastSendToResponse[key], null, '\t') } - ); - } else { - return ( - { _lastSendToResponse[key] } - ); - } - } else if (key === 'sendrawtransaction') { - const _lastSendToResponse = this.props.ActiveCoin.lastSendToResponse; - - if (_lastSendToResponse[key] === 'success') { - return ( - true - ); - } else { - return ( - false - ); - } - } else if (key === 'txid' || key === 'sent') { - const _lastSendToResponse = this.props.ActiveCoin.lastSendToResponse; - - return ( - { _lastSendToResponse[key] } - ); - } else if (key === 'tag') { - return null; + handleSubmit() { + if (!this.validateSendFormData()) { + return; } - } - renderSendCoinResponse() { - return SendCoinResponseRender.call(this); - } - - // experimental, ask @kolo for details if required - getOAdress() { - resolveOpenAliasAddress(this.state.sendToOA) - .then(function(json) { - const reply = json.Answer; - - if (reply && - reply.length) { - for (let i = 0; i < reply.length; i++) { - const _address = reply[i].data.split(' '); - const coin = _address[0].replace('"oa1:', ''); - const coinAddress = _address[1].replace('recipient_address=', '').replace(';', ''); - - if (coin.toUpperCase() === this.props.ActiveCoin.coin) { - this.setState(Object.assign({}, this.state, { - sendTo: coinAddress, - })); - } - } + Store.dispatch( + sendNativeTx( + this.props.ActiveCoin.coin, + this.state + ) + ); - if (this.state.sendTo === '') { - Store.dispatch( - triggerToaster( - 'Couldn\'t find any ' + this.props.ActiveCoin.coin + ' addresses', - 'OpenAlias', - 'error' - ) - ); - } - } else { + if (this.state.addressType === 'private') { + setTimeout(() => { Store.dispatch( - triggerToaster( - 'Couldn\'t find any addresses', - 'OpenAlias', - 'error' + getKMDOPID( + null, + this.props.ActiveCoin.coin ) ); - } - }.bind(this)); - } - - renderOASendUI() { - if (Config.openAlias) { - return OASendUIRender.call(this); + }, 1000); } - - return null; - } - - renderSendApiTypeSelector() { - if (this.props.ActiveCoin.mode === 'basilisk') { - return SendApiTypeSelectorRender.call(this); - } - - return null; - } - - // TODO same as in walletsNav and receiveCoin, find a way to reuse it? - checkBalance() { - let _balance = '0'; - const _mode = this.props.ActiveCoin.mode; - - if (_mode === 'full') { - _balance = this.props.ActiveCoin.balance || 0; - } else if (_mode === 'basilisk') { - if (this.props.ActiveCoin.cache) { - const _cache = this.props.ActiveCoin.cache; - const _coin = this.props.ActiveCoin.coin; - const _address = this.props.ActiveCoin.activeAddress; - - if (_address && - _cache[_coin] && - _cache[_coin][_address] && - _cache[_coin][_address].getbalance && - _cache[_coin][_address].getbalance.data && - (_cache[_coin][_address].getbalance.data.balance || - _cache[_coin][_address].getbalance.data.interest)) { - const _regBalance = _cache[_coin][_address].getbalance.data.balance ? _cache[_coin][_address].getbalance.data.balance : 0; - - _balance = _regBalance; - } - } - } - - return _balance; } // TODO: reduce to a single toast validateSendFormData() { let valid = true; - if (!this.state.sendTo || this.state.sendTo.length < 34) { + + if (!this.state.sendTo || + this.state.sendTo.length < 34) { Store.dispatch( triggerToaster( translate('SEND.SEND_TO_ADDRESS_MIN_LENGTH'), - '', + translate('TOASTR.WALLET_NOTIFICATION'), 'error' ) ); @@ -836,82 +427,93 @@ class SendCoin extends React.Component { Store.dispatch( triggerToaster( translate('SEND.AMOUNT_POSITIVE_NUMBER'), - '', + translate('TOASTR.WALLET_NOTIFICATION'), 'error' ) ); valid = false; } - if (!isPositiveNumber(this.state.fee)) { + if (((!this.state.sendFrom || this.state.addressType === 'public') && + this.state.sendTo && + this.state.sendTo.length === 34 && + this.props.ActiveCoin.balance && + this.props.ActiveCoin.balance.transparent && + Number(this.state.amount) > Number(this.props.ActiveCoin.balance.transparent)) || + (this.state.addressType === 'public' && + this.state.sendTo && + this.state.sendTo.length > 34 && + Number(this.state.amount) > Number(this.state.sendFromAmount)) || + (this.state.addressType === 'private' && + this.state.sendTo && + this.state.sendTo.length >= 34 && + Number(this.state.amount) > Number(this.state.sendFromAmount))) { Store.dispatch( triggerToaster( - translate('SEND.FEE_POSITIVE_NUMBER'), - '', + translate('SEND.INSUFFICIENT_FUNDS'), + translate('TOASTR.WALLET_NOTIFICATION'), 'error' ) ); valid = false; } - if (!isPositiveNumber(this.getTotalAmount())) { + if (this.state.sendTo.length > 34 && + (!this.state.sendFrom || this.state.sendFrom.length < 34)) { Store.dispatch( triggerToaster( - translate('SEND.TOTAL_AMOUNT_POSITIVE_NUMBER'), - '', + translate('SEND.SELECT_SOURCE_ADDRESS'), + translate('TOASTR.WALLET_NOTIFICATION'), 'error' ) ); valid = false; } - /*if ((this.props.ActiveCoin.mode === 'basilisk' && Number(this.state.amount) > Number(this.state.sendFromAmount)) || - (this.props.ActiveCoin.mode === 'full' && Number(this.state.amount) > Number(this.checkBalance()))) { - Store.dispatch( - triggerToaster( - translate('SEND.INSUFFICIENT_FUNDS'), - '', - 'error' - ) - ); - valid = false; - }*/ - return valid; } - getTotalAmount() { - return Number(this.state.amount) - Number(this.state.fee); + isTransparentTx() { + if (((this.state.sendFrom && this.state.sendFrom.length === 34) || !this.state.sendFrom) && + (this.state.sendTo && this.state.sendTo.length === 34)) { + return true; + } + + return false; } render() { - if (this.props.ActiveCoin && - this.props.ActiveCoin.send && - this.props.ActiveCoin.mode !== 'native') { - return SendCoinRender.call(this); + if (this.props && + this.props.ActiveCoin && + (this.props.ActiveCoin.activeSection === 'send' || this.props.activeSection === 'send')) { + return SendRender.call(this); } return null; } } -const mapStateToProps = (state) => { - return { +const mapStateToProps = (state, props) => { + let _mappedProps = { ActiveCoin: { + addresses: state.ActiveCoin.addresses, coin: state.ActiveCoin.coin, mode: state.ActiveCoin.mode, - send: state.ActiveCoin.send, - receive: state.ActiveCoin.receive, + opids: state.ActiveCoin.opids, balance: state.ActiveCoin.balance, - cache: state.ActiveCoin.cache, - activeAddress: state.ActiveCoin.activeAddress, + activeSection: state.ActiveCoin.activeSection, lastSendToResponse: state.ActiveCoin.lastSendToResponse, - addresses: state.ActiveCoin.addresses, - }, - Dashboard: { - activeHandle: state.Dashboard.activeHandle, }, }; + + if (props && + props.activeSection && + props.renderFormOnly) { + _mappedProps.ActiveCoin.activeSection = props.activeSection; + _mappedProps.renderFormOnly = props.renderFormOnly; + } + + return _mappedProps; }; -export default connect(mapStateToProps)(SendCoin); \ No newline at end of file +export default connect(mapStateToProps)(SendCoin); diff --git a/react/src/components/dashboard/sendCoin/sendCoin.render.js b/react/src/components/dashboard/sendCoin/sendCoin.render.js index 4ca3d8d..995cb68 100644 --- a/react/src/components/dashboard/sendCoin/sendCoin.render.js +++ b/react/src/components/dashboard/sendCoin/sendCoin.render.js @@ -1,402 +1,327 @@ import React from 'react'; import { translate } from '../../../translate/translate'; -import { - secondsElapsedToString, - secondsToString -} from '../../../util/time'; - import QRModal from '../qrModal/qrModal'; -export const UTXOCacheInfoRender = function(refreshCacheData, isReadyToUpdate, waitUntilCallIsFinished, timestamp) { - const _progress = 100 - this.state.currentStackLength * 100 / this.state.totalStackLength; - +export const AddressListRender = function() { return ( -
    -
    - { translate('SEND.TOTAL_UTXO_AVAILABLE') }: - { refreshCacheData ? refreshCacheData.data && refreshCacheData.data.length : translate('SEND.PRESS_UPDATE_BTN') }
    -
    - { translate('SEND.LAST_UPDATED') } @ - { secondsToString(refreshCacheData ? refreshCacheData.timestamp : 0, true) } |  - { secondsElapsedToString(timestamp || 0) }&nbps; - { translate('SEND.AGO') }
    -
    -
    - { translate('SEND.NEXT_UPDATE_IN') } { secondsElapsedToString(600 - timestamp) }s -
    -
    -
    - { translate('SEND.PROCESSING_REQ') }: { this.state.currentStackLength } / { this.state.totalStackLength } -
    -
    +
    +
    + +
    ); }; -export const SendCoinResponseRender = function() { - if (this.props.ActiveCoin.lastSendToResponse) { - let items = []; - const _response = this.props.ActiveCoin.lastSendToResponse; - - for (let key in _response) { - if (key !== 'tag') { - items.push( - - { key } - { this.renderKey(key) } - - ); +export const _SendFormRender = function() { + return ( +
    + { this.state.renderAddressDropdown && +
    +
    + + { this.renderAddressList() } +
    +
    } - } +
    +
    + + +
    +
    + + +
    +
    + + +
    this.toggleSubstractFee() }> + { translate('DASHBOARD.SUBSTRACT_FEE') } +
    +
    +
    +
    + + +
    +
    + + { translate('INDEX.TOTAL') }:  + { this.state.amount } - { this.state.fee }/kb = { Number(this.state.amount) - Number(this.state.fee) }  + { this.props.ActiveCoin.coin } + +
    +
    + +
    +
    +
    + ); +} - return items; +export const SendRender = function() { + if (this.props.renderFormOnly) { + return ( +
    { this.SendFormRender() }
    + ); } else { return ( - - -
    -
    - { translate('SEND.PROCESSING_TRANSACTION') }...
    - { translate('SEND.NOTE_IT_WILL_TAKE') }. -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    +
    +
    +
    +
    + 1 +
    + { translate('INDEX.FILL_SEND_FORM') } +

    { translate('INDEX.FILL_SEND_DETAILS') }

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    +
    +
    + 2 +
    + { translate('INDEX.CONFIRMING') } +

    { translate('INDEX.CONFIRM_DETAILS') }

    -
    -
    -
    -
    -
    -
    -
    -
    +
    +
    + 3 +
    + { translate('INDEX.PROCESSING_TX') } +

    { translate('INDEX.PROCESSING_DETAILS') }

    - - - ); - } -} - -export const OASendUIRender = function() { - return ( -
    -
    - - -
    -
    - -
    -
    - ); -}; - -export const SendApiTypeSelectorRender = function() { - return ( -
    -
    - - -
    - { translate('SEND.SEND_VIA') } (sendtoaddress API) -
    -
    -
    -
    - -
    -
    - ); -}; +
    -export const SendCoinRender = function() { - return ( -
    -
    -
    -
    - 1 -
    - { translate('INDEX.FILL_SEND_FORM') } -

    { translate('INDEX.FILL_SEND_DETAILS') }

    +
    +
    +
    +

    + { translate('INDEX.SEND') } { this.props.ActiveCoin.coin } +

    -
    -
    - 2 -
    - { translate('INDEX.CONFIRMING') } -

    { translate('INDEX.CONFIRM_DETAILS') }

    +
    +
    -
    -
    - 3 -
    - { translate('INDEX.PROCESSING_TX') } -

    { translate('INDEX.PROCESSING_DETAILS') }

    +
    + { this.SendFormRender() }
    -
    -
    -

    - { translate('INDEX.SEND') } { this.props.ActiveCoin.coin } -

    -
    -
    -
    - { this.renderSendApiTypeSelector() } -
    -
    - - { this.renderAddressList() } -
    -
    - { this.renderOASendUI() } +
    +
    +
    -
    - - -
    -
    - - +
    + { translate('INDEX.TO') }
    -
    - - +
    { this.state.sendTo }
    +
    + { this.state.amount } { this.props.ActiveCoin.coin }
    -
    - - { translate('INDEX.TOTAL') }  - ({ translate('INDEX.AMOUNT_SM') } - { translate('INDEX.FEE') }): -   - { this.getTotalAmount() } { this.props.ActiveCoin.coin } -
    -
    - - -
    - { translate('INDEX.DONT_SEND') } -
    -
    +
    { translate('DASHBOARD.SUBSTRACT_FEE') }
    +
    + + { this.state.sendFrom && +
    +
    + { translate('INDEX.FROM') } +
    +
    { this.state.sendFrom }
    +
    + { Number(this.state.amount) } { this.props.ActiveCoin.coin } +
    - { this.renderUTXOCacheInfo()} -
    + } +
    + this.changeSendCoinStep(0, true) }>{ translate('INDEX.BACK') } +
    - +
    -
    -
    -
    -
    -
    -
    - { translate('INDEX.TO') } -
    -
    { this.state.sendTo }
    -
    - { this.state.amount } { this.props.ActiveCoin.coin } -
    -
    { translate('INDEX.TX_FEE_REQ') }
    -
    - { this.state.fee } { this.props.ActiveCoin.coin } -
    -
    -
    - -
    -
    - { translate('INDEX.FROM') } -
    -
    - { this.props.Dashboard.activeHandle[this.props.ActiveCoin.coin] } -
    -
    - { Number(this.state.amount) - Number(this.state.fee) } { this.props.ActiveCoin.coin } +
    +
    +
    +

    + { translate('INDEX.TRANSACTION_RESULT') } +

    +
    + + + + + + + + + + + + + + + + + +
    { translate('INDEX.KEY') }{ translate('INDEX.INFO') }
    + Result + + success +
    Transaction ID{ this.state.lastSendToResponse }
    -
    -
    - this.changeSendCoinStep(0) }>{ translate('INDEX.BACK') } -
    - +
    +
    + +
    -
    -
    -
    -
    -

    - { translate('INDEX.TRANSACTION_RESULT') } -

    -
    - { translate('SEND.YOU_PICKED_OPT') } -
    - - - - - - - - - { this.renderSendCoinResponse() } - -
    { translate('INDEX.KEY') }{ translate('INDEX.INFO') }
    -
    -
    - + { this.renderOPIDListCheck() && +
    +
    +
    +
    +
    +
    +
    +

    + { translate('INDEX.OPERATIONS_STATUSES') } +

    +
    +
    + + + + + + + + + + + { this.renderOPIDList() } + + + + + + + + + +
    { translate('INDEX.STATUS') }ID{ translate('INDEX.TIME') }{ translate('INDEX.RESULT') }
    { translate('INDEX.STATUS') }ID{ translate('INDEX.TIME') }{ translate('INDEX.RESULT') }
    +
    +
    +
    +
    -
    + }
    -
    - ); + ); + } }; \ No newline at end of file diff --git a/react/src/components/dashboard/walletsNative/walletsNative.render.js b/react/src/components/dashboard/walletsNative/walletsNative.render.js index a8852fb..5aa987f 100644 --- a/react/src/components/dashboard/walletsNative/walletsNative.render.js +++ b/react/src/components/dashboard/walletsNative/walletsNative.render.js @@ -1,7 +1,7 @@ import React from 'react'; import WalletsBalance from '../walletsBalance/walletsBalance'; import WalletsInfo from '../walletsInfo/walletsInfo'; -import WalletsNativeSend from '../walletsNativeSend/walletsNativeSend'; +import SendCoin from '../sendCoin/sendCoin'; import WalletsProgress from '../walletsProgress/walletsProgress'; import WalletsData from '../walletsData/walletsData'; import ReceiveCoin from '../receiveCoin/receiveCoin'; @@ -33,7 +33,7 @@ const WalletsNativeRender = function() { - +
    diff --git a/react/src/components/dashboard/walletsNativeSend/walletsNativeSend.js b/react/src/components/dashboard/walletsNativeSend/walletsNativeSend.js deleted file mode 100644 index 0d4747a..0000000 --- a/react/src/components/dashboard/walletsNativeSend/walletsNativeSend.js +++ /dev/null @@ -1,460 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import Config from '../../../config'; -import { translate } from '../../../translate/translate'; -import { secondsToString } from '../../../util/time'; -import { - triggerToaster, - sendNativeTx, - getKMDOPID -} from '../../../actions/actionCreators'; -import Store from '../../../store'; -import { - AddressListRender, - WalletsNativeSendRender, - WalletsNativeSendFormRender, - _WalletsNativeSendFormRender -} from './walletsNativeSend.render'; -import { isPositiveNumber } from '../../../util/number'; - -class WalletsNativeSend extends React.Component { - constructor(props) { - super(props); - this.state = { - addressType: null, - sendFrom: null, - sendFromAmount: 0, - sendTo: '', - amount: 0, - fee: 0, - addressSelectorOpen: false, - renderAddressDropdown: true, - substractFee: false, - }; - this.updateInput = this.updateInput.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - this.openDropMenu = this.openDropMenu.bind(this); - this.handleClickOutside = this.handleClickOutside.bind(this); - this.checkZAddressCount = this.checkZAddressCount.bind(this); - this.setRecieverFromScan = this.setRecieverFromScan.bind(this); - this.renderOPIDListCheck = this.renderOPIDListCheck.bind(this); - this.WalletsNativeSendFormRender = _WalletsNativeSendFormRender.bind(this); - this.isTransparentTx = this.isTransparentTx.bind(this); - this.toggleSubstractFee = this.toggleSubstractFee.bind(this); - } - - // TODO: 1) t -> z amount validation - // 2) z -> z amount validation - - WalletsNativeSendFormRender() { - return _WalletsNativeSendFormRender.call(this); - } - - toggleSubstractFee() { - this.setState({ - substractFee: !this.state.substractFee, - }); - } - - componentWillMount() { - document.addEventListener( - 'click', - this.handleClickOutside, - false - ); - } - - componentWillUnmount() { - document.removeEventListener( - 'click', - this.handleClickOutside, - false - ); - } - - componentWillReceiveProps() { - this.checkZAddressCount(); - } - - setRecieverFromScan(receiver) { - try { - const recObj = JSON.parse(receiver); - - if (recObj && - typeof recObj === 'object') { - if (recObj.coin === this.props.ActiveCoin.coin) { - if (recObj.amount) { - this.setState({ - amount: recObj.amount, - }); - } - if (recObj.address) { - this.setState({ - sendTo: recObj.address, - }); - } - } else { - Store.dispatch( - triggerToaster( - translate('SEND.QR_COIN_MISMATCH_MESSAGE_IMPORT_COIN') + - recObj.coin + - translate('SEND.QR_COIN_MISMATCH_MESSAGE_ACTIVE_COIN') + - this.props.ActiveCoin.coin + - translate('SEND.QR_COIN_MISMATCH_MESSAGE_END'), - translate('SEND.QR_COIN_MISMATCH_TITLE'), - 'warning' - ) - ); - } - } - } catch (e) { - this.setState({ - sendTo: receiver, - }); - } - - document.getElementById('kmdWalletSendTo').focus(); - } - - handleClickOutside(e) { - if (e.srcElement.className !== 'btn dropdown-toggle btn-info' && - (e.srcElement.offsetParent && e.srcElement.offsetParent.className !== 'btn dropdown-toggle btn-info') && - (e.path && e.path[4] && e.path[4].className.indexOf('showkmdwalletaddrs') === -1)) { - this.setState({ - addressSelectorOpen: false, - }); - } - } - - checkZAddressCount() { - const _addresses = this.props.ActiveCoin.addresses; - - if (_addresses && - (!_addresses.private || - _addresses.private.length === 0)) { - this.setState({ - renderAddressDropdown: false, - }); - } else { - this.setState({ - renderAddressDropdown: true, - }); - } - } - - renderAddressByType(type) { - let _items = []; - - if (this.props.ActiveCoin.addresses && - this.props.ActiveCoin.addresses[type] && - this.props.ActiveCoin.addresses[type].length) { - this.props.ActiveCoin.addresses[type].map((address) => { - if (address.amount > 0) { - _items.push( -
  • - this.updateAddressSelection(address.address, type, address.amount) }> -    - - [ { address.amount } { this.props.ActiveCoin.coin } ]   - { type === 'public' ? address.address : address.address.substring(0, 34) + '...' } - - - -
  • - ); - } - }); - - return _items; - } else { - return null; - } - } - - renderOPIDListCheck() { - if (this.state.renderAddressDropdown && - this.props.ActiveCoin.opids && - this.props.ActiveCoin.opids.length) { - return true; - } - } - - renderSelectorCurrentLabel() { - if (this.state.sendFrom) { - return ( - - - - [ { this.state.sendFromAmount } { this.props.ActiveCoin.coin } ]   - { this.state.addressType === 'public' ? this.state.sendFrom : this.state.sendFrom.substring(0, 34) + '...' } - - - ); - } else { - return ( - { translate('INDEX.T_FUNDS') } - ); - } - } - - renderAddressList() { - return AddressListRender.call(this); - } - - renderOPIDLabel(opid) { - const _satatusDef = { - queued: { - icon: 'warning', - label: 'QUEUED', - }, - executing: { - icon: 'info', - label: 'EXECUTING', - }, - failed: { - icon: 'danger', - label: 'FAILED', - }, - success: { - icon: 'success', - label: 'SUCCESS', - }, - }; - - return ( - -   - { translate(`KMD_NATIVE.${_satatusDef[opid.status].label}`) } - - ); - } - - renderOPIDResult(opid) { - let isWaitingStatus = true; - - if (opid.status === 'queued') { - isWaitingStatus = false; - return ( - { translate('SEND.AWAITING') }... - ); - } - if (opid.status === 'executing') { - isWaitingStatus = false; - return ( - { translate('SEND.PROCESSING') }... - ); - } - if (opid.status === 'failed') { - isWaitingStatus = false; - return ( - - { translate('SEND.ERROR_CODE') }: { opid.error.code } -
    - { translate('KMD_NATIVE.MESSAGE') }: { opid.error.message } -
    - ); - } - if (opid.status === 'success') { - isWaitingStatus = false; - return ( - - { translate('KMD_NATIVE.TXID') }: { opid.result.txid } -
    - { translate('KMD_NATIVE.EXECUTION_SECONDS') }: { opid.execution_secs } -
    - ); - } - if (isWaitingStatus) { - return ( - { translate('SEND.WAITING') }... - ); - } - } - - renderOPIDList() { - if (this.props.ActiveCoin.opids && - this.props.ActiveCoin.opids.length) { - return this.props.ActiveCoin.opids.map((opid) => - - { this.renderOPIDLabel(opid) } - { opid.id } - { secondsToString(opid.creation_time) } - { this.renderOPIDResult(opid) } - - ); - } else { - return null; - } - } - - openDropMenu() { - this.setState(Object.assign({}, this.state, { - addressSelectorOpen: !this.state.addressSelectorOpen, - })); - } - - updateAddressSelection(address, type, amount) { - this.setState(Object.assign({}, this.state, { - sendFrom: address, - addressType: type, - sendFromAmount: amount, - addressSelectorOpen: !this.state.addressSelectorOpen, - })); - } - - updateInput(e) { - this.setState({ - [e.target.name]: e.target.value, - }); - } - - handleSubmit() { - if (!this.validateSendFormData()) { - return; - } - - Store.dispatch( - sendNativeTx( - this.props.ActiveCoin.coin, - this.state - ) - ); - - if (this.state.addressType === 'private') { - setTimeout(() => { - Store.dispatch( - getKMDOPID( - null, - this.props.ActiveCoin.coin - ) - ); - }, 1000); - } - - this.setState({ - addressType: null, - sendFrom: null, - sendFromAmount: 0, - sendTo: '', - sendToOA: null, - amount: 0, - fee: 0, - addressSelectorOpen: false, - renderAddressDropdown: true, - substractFee: false, - }); - } - - // TODO: reduce to a single toast - validateSendFormData() { - let valid = true; - - if (!this.state.sendTo || - this.state.sendTo.length < 34) { - Store.dispatch( - triggerToaster( - translate('SEND.SEND_TO_ADDRESS_MIN_LENGTH'), - translate('TOASTR.WALLET_NOTIFICATION'), - 'error' - ) - ); - valid = false; - } - - if (!isPositiveNumber(this.state.amount)) { - Store.dispatch( - triggerToaster( - translate('SEND.AMOUNT_POSITIVE_NUMBER'), - translate('TOASTR.WALLET_NOTIFICATION'), - 'error' - ) - ); - valid = false; - } - - if (((!this.state.sendFrom || this.state.addressType === 'public') && - this.state.sendTo && - this.state.sendTo.length === 34 && - this.props.ActiveCoin.balance && - this.props.ActiveCoin.balance.transparent && - Number(this.state.amount) > Number(this.props.ActiveCoin.balance.transparent)) || - (this.state.addressType === 'public' && - this.state.sendTo && - this.state.sendTo.length > 34 && - Number(this.state.amount) > Number(this.state.sendFromAmount)) || - (this.state.addressType === 'private' && - this.state.sendTo && - this.state.sendTo.length >= 34 && - Number(this.state.amount) > Number(this.state.sendFromAmount))) { - Store.dispatch( - triggerToaster( - translate('SEND.INSUFFICIENT_FUNDS'), - translate('TOASTR.WALLET_NOTIFICATION'), - 'error' - ) - ); - valid = false; - } - - if (this.state.sendTo.length > 34 && - (!this.state.sendFrom || this.state.sendFrom.length < 34)) { - Store.dispatch( - triggerToaster( - translate('SEND.SELECT_SOURCE_ADDRESS'), - translate('TOASTR.WALLET_NOTIFICATION'), - 'error' - ) - ); - valid = false; - } - - return valid; - } - - isTransparentTx() { - if (((this.state.sendFrom && this.state.sendFrom.length === 34) || !this.state.sendFrom) && - (this.state.sendTo && this.state.sendTo.length === 34)) { - return true; - } - - return false; - } - - render() { - if (this.props && - this.props.ActiveCoin && - (this.props.ActiveCoin.activeSection === 'send' || this.props.activeSection === 'send')) { - return WalletsNativeSendRender.call(this); - } - - return null; - } -} - -const mapStateToProps = (state, props) => { - let _mappedProps = { - ActiveCoin: { - addresses: state.ActiveCoin.addresses, - coin: state.ActiveCoin.coin, - mode: state.ActiveCoin.mode, - opids: state.ActiveCoin.opids, - balance: state.ActiveCoin.balance, - activeSection: state.ActiveCoin.activeSection, - }, - }; - - if (props && - props.activeSection && - props.renderFormOnly) { - _mappedProps.ActiveCoin.activeSection = props.activeSection; - _mappedProps.renderFormOnly = props.renderFormOnly; - } - - return _mappedProps; -}; - -export default connect(mapStateToProps)(WalletsNativeSend); diff --git a/react/src/components/dashboard/walletsNativeSend/walletsNativeSend.render.js b/react/src/components/dashboard/walletsNativeSend/walletsNativeSend.render.js deleted file mode 100644 index c165fab..0000000 --- a/react/src/components/dashboard/walletsNativeSend/walletsNativeSend.render.js +++ /dev/null @@ -1,213 +0,0 @@ -import React from 'react'; -import { translate } from '../../../translate/translate'; -import QRModal from '../qrModal/qrModal'; - -export const AddressListRender = function() { - return ( -
    - -
    - -
    -
    - ); -}; - -export const _WalletsNativeSendFormRender = function() { - return ( -
    - { this.state.renderAddressDropdown && -
    -
    - - { this.renderAddressList() } -
    -
    - } -
    -
    - - -
    -
    - - -
    -
    - - -
    this.toggleSubstractFee() }> - { translate('DASHBOARD.SUBSTRACT_FEE') } -
    -
    -
    -
    - - -
    -
    - - { translate('INDEX.TOTAL') }:  - { this.state.amount } - { this.state.fee }/kb = { Number(this.state.amount) - Number(this.state.fee) }  - { this.props.ActiveCoin.coin } - -
    -
    - -
    -
    -
    - ); -} - -export const WalletsNativeSendRender = function() { - if (this.props.renderFormOnly) { - return ( -
    { this.WalletsNativeSendFormRender() }
    - ); - } else { - return ( -
    -
    -
    -
    -

    - { translate('INDEX.SEND') } { this.props.ActiveCoin.coin } -

    -
    -
    - -
    -
    - { this.WalletsNativeSendFormRender() } -
    -
    -
    - - { this.renderOPIDListCheck() && -
    -
    -
    -
    -
    -
    -
    -

    - { translate('INDEX.OPERATIONS_STATUSES') } -

    -
    -
    - - - - - - - - - - - { this.renderOPIDList() } - - - - - - - - - -
    { translate('INDEX.STATUS') }ID{ translate('INDEX.TIME') }{ translate('INDEX.RESULT') }
    { translate('INDEX.STATUS') }ID{ translate('INDEX.TIME') }{ translate('INDEX.RESULT') }
    -
    -
    -
    -
    -
    -
    -
    - } -
    - ); - } -}; \ No newline at end of file diff --git a/react/src/components/overrides.scss b/react/src/components/overrides.scss index 0f942bb..83ca8d1 100644 --- a/react/src/components/overrides.scss +++ b/react/src/components/overrides.scss @@ -402,4 +402,21 @@ select{ .bold { font-weight: bold; +} + +.modal { + .modal-content { + .modal-header { + .close { + font-size: 28px; + position: absolute; + z-index: 100; + right: 20px; + opacity: 0.5; + } + .close:hover { + opacity: 1; + } + } + } } \ No newline at end of file