diff --git a/react/src/actions/actionCreators.js b/react/src/actions/actionCreators.js index 68e99e4..5919f83 100644 --- a/react/src/actions/actionCreators.js +++ b/react/src/actions/actionCreators.js @@ -27,6 +27,7 @@ import { DASHBOARD_ACTIVE_COIN_NATIVE_TXHISTORY, DISPLAY_LOGIN_SETTINGS_MODAL, DISPLAY_COIND_DOWN_MODAL, + DISPLAY_CLAIM_INTEREST_MODAL, START_INTERVAL, STOP_INTERVAL } from './storeType'; @@ -69,6 +70,7 @@ export * from './actions/iguanaHelpers'; export * from './actions/cli'; export * from './actions/update'; export * from './actions/jumblr'; +export * from './actions/interest'; export function changeActiveAddress(address) { return { @@ -367,4 +369,11 @@ export function toggleLoginSettingsModal(display) { type: DISPLAY_LOGIN_SETTINGS_MODAL, displayLoginSettingsModal: display, } +} + +export function toggleClaimInterestModal(display) { + return { + type: DISPLAY_CLAIM_INTEREST_MODAL, + displayClaimInterestModal: display, + } } \ No newline at end of file diff --git a/react/src/actions/actions/interest.js b/react/src/actions/actions/interest.js new file mode 100644 index 0000000..1dd95c6 --- /dev/null +++ b/react/src/actions/actions/interest.js @@ -0,0 +1,86 @@ +import { + triggerToaster +} from '../actionCreators'; +import { + logGuiHttp, + guiLogState +} from './log'; +import Config from '../../config'; + +export function getListUnspent(coin) { + return new Promise((resolve, reject) => { + const payload = { + mode: null, + chain: coin, + cmd: 'listunspent', + }; + + const _fetchConfig = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ 'payload': payload }), + }; + + fetch( + `http://127.0.0.1:${Config.agamaPort}/shepherd/cli`, + _fetchConfig + ) + .catch(function(error) { + console.log(error); + dispatch( + triggerToaster( + 'getListUnspent', + 'Error', + 'error' + ) + ); + }) + .then(response => response.json()) + .then(json => { + resolve(json.result ? json.result : json); + }) + }); +} + +export function getRawTransaction(coin, txid) { + return new Promise((resolve, reject) => { + const payload = { + mode: null, + chain: coin, + cmd: 'getrawtransaction', + params: [ + txid, + 1 + ], + }; + + const _fetchConfig = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ 'payload': payload }), + }; + + fetch( + `http://127.0.0.1:${Config.agamaPort}/shepherd/cli`, + _fetchConfig + ) + .catch(function(error) { + console.log(error); + dispatch( + triggerToaster( + 'getTransaction', + 'Error', + 'error' + ) + ); + }) + .then(response => response.json()) + .then(json => { + resolve(json.result ? json.result : json); + }) + }); +} \ No newline at end of file diff --git a/react/src/actions/actions/log.js b/react/src/actions/actions/log.js index ca42d5b..c53f402 100644 --- a/react/src/actions/actions/log.js +++ b/react/src/actions/actions/log.js @@ -41,7 +41,6 @@ export function getAgamaLog(type) { ); }) .then(response => response.json()) - .then() } } diff --git a/react/src/actions/actions/nativeSend.js b/react/src/actions/actions/nativeSend.js index 0f788c8..fc3e3e2 100644 --- a/react/src/actions/actions/nativeSend.js +++ b/react/src/actions/actions/nativeSend.js @@ -195,7 +195,7 @@ export function getKMDOPID(opid, coin) { passthruAgent = getPassthruAgent(coin), tmpIguanaRPCAuth = `tmpIgRPCUser@${sessionStorage.getItem('IguanaRPCAuth')}`; - if (passthruAgent == 'iguana') { + if (passthruAgent === 'iguana') { payload = { 'userpass': tmpIguanaRPCAuth, 'agent': passthruAgent, @@ -284,4 +284,48 @@ export function getKMDOPID(opid, coin) { }) }) } +} + +export function sendToAddressPromise(coin, address, amount) { + return new Promise((resolve, reject) => { + const payload = { + mode: null, + chain: coin, + cmd: 'sendtoaddress', + params: [ + address, + amount, + 'KMD interest claim request', + 'KMD interest claim request', + true + ] + }; + + const _fetchConfig = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ 'payload': payload }), + }; + + fetch( + `http://127.0.0.1:${Config.agamaPort}/shepherd/cli`, + _fetchConfig + ) + .catch(function(error) { + console.log(error); + dispatch( + triggerToaster( + 'sendToAddress', + 'Error', + 'error' + ) + ); + }) + .then(response => response.json()) + .then(json => { + resolve(json); + }) + }); } \ No newline at end of file diff --git a/react/src/actions/actions/nativeSyncInfo.js b/react/src/actions/actions/nativeSyncInfo.js index 196ef53..b75b161 100644 --- a/react/src/actions/actions/nativeSyncInfo.js +++ b/react/src/actions/actions/nativeSyncInfo.js @@ -13,7 +13,7 @@ import Config from '../../config'; export function getSyncInfoNativeKMD(skipDebug, json) { const coin = 'KMD'; - + // https://www.kmd.host/ return dispatch => { const _timestamp = Date.now(); if (Config.debug) { diff --git a/react/src/actions/storeType.js b/react/src/actions/storeType.js index 23deb33..1e4c8af 100644 --- a/react/src/actions/storeType.js +++ b/react/src/actions/storeType.js @@ -45,4 +45,5 @@ export const LOG_GUI_HTTP = 'LOG_GUI_HTTP'; export const CLI = 'CLI'; export const LOGOUT = 'LOGOUT'; export const DISPLAY_COIND_DOWN_MODAL = 'DISPLAY_COIND_DOWN_MODAL'; -export const DISPLAY_LOGIN_SETTINGS_MODAL = 'DISPLAY_LOGIN_SETTINGS_MODAL'; \ No newline at end of file +export const DISPLAY_LOGIN_SETTINGS_MODAL = 'DISPLAY_LOGIN_SETTINGS_MODAL'; +export const DISPLAY_CLAIM_INTEREST_MODAL = 'DISPLAY_CLAIM_INTEREST_MODAL'; \ No newline at end of file diff --git a/react/src/components/dashboard/claimInterestModal/claimInterestModal.js b/react/src/components/dashboard/claimInterestModal/claimInterestModal.js new file mode 100755 index 0000000..94e05f5 --- /dev/null +++ b/react/src/components/dashboard/claimInterestModal/claimInterestModal.js @@ -0,0 +1,136 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Store from '../../../store'; +import { + toggleClaimInterestModal, + getListUnspent, + getRawTransaction, + copyString, + sendToAddressPromise, + triggerToaster +} from '../../../actions/actionCreators'; +import { translate } from '../../../translate/translate'; +import { + ClaimInterestModalRender, + _ClaimInterestTableRender +} from './claimInterestModal.render'; + +class ClaimInterestModal extends React.Component { + constructor(props) { + super(props); + this.state = { + open: false, + isLoading: true, + transactionsList: [], + showZeroInterest: true, + }; + this.claimInterestTableRender = this.claimInterestTableRender.bind(this); + this.toggleZeroInterest = this.toggleZeroInterest.bind(this); + this.loadListUnspent = this.loadListUnspent.bind(this); + this.checkTransactionsListLength = this.checkTransactionsListLength.bind(this); + } + + componentWillMount() { + this.loadListUnspent(); + } + + loadListUnspent() { + let _transactionsList = []; + + getListUnspent(this.props.ActiveCoin.coin) + .then((json) => { + if (json && + json.length) { + for (let i = 0; i < json.length; i++) { + getRawTransaction(this.props.ActiveCoin.coin, json[i].txid) + .then((_json) => { + _transactionsList.push({ + address: json[i].address, + locktime: _json.locktime, + amount: json[i].amount, + interest: json[i].interest, + txid: json[i].txid, + }); + + if (i === json.length - 1) { + this.setState({ + transactionsList: _transactionsList, + isLoading: false, + }); + } + }); + } + } + }); + } + + claimInterest(address, amount) { + sendToAddressPromise(this.props.ActiveCoin.coin, this.state.transactionsList[0].address, this.props.ActiveCoin.balance.transparent) + .then((json) => { + if (json.error && + json.error.code) { + Store.dispatch( + triggerToaster( + json.error.message, + 'Error', + 'error' + ) + ); + } else if (json.result && json.result.length && json.result.length === 64) { + Store.dispatch( + triggerToaster( + `Your full balance is sent to address ${this.state.transactionsList[0].address}. Check back your new balance in a few minutes.`, + translate('TOASTR.WALLET_NOTIFICATION'), + 'success', + false + ) + ); + } + }); + } + + checkTransactionsListLength() { + if (this.state.transactionsList && this.state.transactionsList.length) { + return true; + } else if (!this.state.transactionsList || !this.state.transactionsList.length) { + return false; + } + } + + toggleZeroInterest() { + this.setState({ + showZeroInterest: !this.state.showZeroInterest, + }); + } + + copyTxId(txid) { + Store.dispatch(copyString(txid, 'Transaction ID copied')); + } + + claimInterestTableRender() { + return _ClaimInterestTableRender.call(this); + } + + componentWillReceiveProps(props) { + if (props.Dashboard.displayClaimInterestModal !== this.state.open) { + this.setState({ + open: props.Dashboard.displayClaimInterestModal, + }); + } + + if (!this.state.open && + props.Dashboard.displayClaimInterestModal) { + this.loadListUnspent(); + } + } + + closeModal() { + Store.dispatch(toggleClaimInterestModal(false)); + } + + render() { + return ClaimInterestModalRender.call(this); + } +} + +export default ClaimInterestModal; \ No newline at end of file diff --git a/react/src/components/dashboard/claimInterestModal/claimInterestModal.render.js b/react/src/components/dashboard/claimInterestModal/claimInterestModal.render.js new file mode 100644 index 0000000..8754965 --- /dev/null +++ b/react/src/components/dashboard/claimInterestModal/claimInterestModal.render.js @@ -0,0 +1,132 @@ +import React from 'react'; +import { translate } from '../../../translate/translate'; + +const MIN_INTEREST_THRESHOLD = 0.001; + +export const _ClaimInterestTableRender = function() { + const _transactionsList = this.state.transactionsList; + let _items = []; + + for (let i = 0; i < _transactionsList.length; i++) { + if ((_transactionsList[i].interest === 0 && this.state.showZeroInterest) || (_transactionsList[i].amount > 0 && _transactionsList[i].interest > 0)) { + _items.push( + + + + + { _transactionsList[i].address } + 10 ? 'green bold' : '' }>{ _transactionsList[i].amount } + { _transactionsList[i].interest } + + { _transactionsList[i].locktime && + + } + { !_transactionsList[i].locktime && + + } + + + ); + } + } + + return ( + +
+ Requirements to accrue interest: locktime field is set and amount is greater than 10 KMD +
+
+ +
+ Show zero interest +
+
+ +
+ + + + + + + + + + + + { _items } + + + + + + + + + + +
AddressAmountInterestLocktime
AddressAmountInterestLocktime
+
+
+ ); +}; + +export const ClaimInterestModalRender = function() { + return ( + +
+
+
+
+ +

Claim interest

+
+
+ +
+
+ { this.state.isLoading && + Loading interest data... + } + { !this.state.isLoading && this.checkTransactionsListLength() && +
{ this.claimInterestTableRender() }
+ } + { !this.state.isLoading && !this.checkTransactionsListLength() && +
No data
+ } +
+
+
+
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/react/src/components/dashboard/jumblr/jumblr.js b/react/src/components/dashboard/jumblr/jumblr.js index d75b948..a69dbd1 100755 --- a/react/src/components/dashboard/jumblr/jumblr.js +++ b/react/src/components/dashboard/jumblr/jumblr.js @@ -169,7 +169,7 @@ class Jumblr extends React.Component { json.error.code) { Store.dispatch( triggerToaster( - json.error.code, + json.error.message, 'Error', 'error' ) @@ -299,7 +299,7 @@ class Jumblr extends React.Component { } else { Store.dispatch( triggerToaster( - json.error.code, + json.error.message, 'Error', 'error' ) @@ -348,7 +348,7 @@ class Jumblr extends React.Component { json.error.code) { Store.dispatch( triggerToaster( - json.error.code, + json.error.message, 'Error', 'error' ) @@ -372,7 +372,7 @@ class Jumblr extends React.Component { } else { Store.dispatch( triggerToaster( - json.error.code, + json.error.message, 'Error', 'error' ) diff --git a/react/src/components/dashboard/navbar/navbar.js b/react/src/components/dashboard/navbar/navbar.js index 6480b97..dfacb44 100755 --- a/react/src/components/dashboard/navbar/navbar.js +++ b/react/src/components/dashboard/navbar/navbar.js @@ -10,6 +10,7 @@ import { } from '../../../actions/actionCreators'; import Store from '../../../store'; import Config from '../../../config'; +import { checkAC } from '../../addcoin/payload'; import NavbarRender from './navbar.render'; @@ -23,6 +24,7 @@ class Navbar extends React.Component { this.openDropMenu = this.openDropMenu.bind(this); this.logout = this.logout.bind(this); this.handleClickOutside = this.handleClickOutside.bind(this); + this._checkAC = this._checkAC.bind(this); } componentWillMount() { @@ -67,6 +69,10 @@ class Navbar extends React.Component { Store.dispatch(dashboardChangeSection(sectionName)); } + _checkAC() { + return checkAC(this.props.ActiveCoin.coin); + } + logout() { Store.dispatch( stopInterval( diff --git a/react/src/components/dashboard/navbar/navbar.render.js b/react/src/components/dashboard/navbar/navbar.render.js index 48816d0..4449f5f 100644 --- a/react/src/components/dashboard/navbar/navbar.render.js +++ b/react/src/components/dashboard/navbar/navbar.render.js @@ -57,7 +57,7 @@ const NavbarRender = function() { BarterDEX - { this.props.ActiveCoin && this.props.ActiveCoin.mode === 'native' && + { this.props.ActiveCoin && this.props.ActiveCoin.mode === 'native' && (this._checkAC() || this.props.ActiveCoin.coin === 'KMD') &&
  • this.dashboardChangeSection('jumblr') }> Jumblr diff --git a/react/src/components/dashboard/walletsNativeInfo/walletsNativeInfo.js b/react/src/components/dashboard/walletsNativeInfo/walletsNativeInfo.js index 6e025e1..4630f4e 100644 --- a/react/src/components/dashboard/walletsNativeInfo/walletsNativeInfo.js +++ b/react/src/components/dashboard/walletsNativeInfo/walletsNativeInfo.js @@ -1,9 +1,16 @@ import React from 'react'; +import { toggleClaimInterestModal } from '../../../actions/actionCreators'; +import Store from '../../../store'; import WalletsNativeInfoRender from './walletsNativeInfo.render'; class WalletsNativeInfo extends React.Component { constructor(props) { super(props); + this.openClaimInterestModal = this.openClaimInterestModal.bind(this); + } + + openClaimInterestModal() { + Store.dispatch(toggleClaimInterestModal(true)); } render() { diff --git a/react/src/components/dashboard/walletsNativeInfo/walletsNativeInfo.render.js b/react/src/components/dashboard/walletsNativeInfo/walletsNativeInfo.render.js index 07bd784..19acf59 100644 --- a/react/src/components/dashboard/walletsNativeInfo/walletsNativeInfo.render.js +++ b/react/src/components/dashboard/walletsNativeInfo/walletsNativeInfo.render.js @@ -1,5 +1,6 @@ import React from 'react'; import { translate } from '../../../translate/translate'; +import ClaimInterestModal from '../claimInterestModal/claimInterestModal'; const WalletsNativeInfoRender = function() { return ( @@ -40,6 +41,15 @@ const WalletsNativeInfoRender = function() { + { this.props.ActiveCoin.coin === 'KMD' && +
    + + +
    + }
    diff --git a/react/src/components/overrides.scss b/react/src/components/overrides.scss index c675967..b074e1f 100644 --- a/react/src/components/overrides.scss +++ b/react/src/components/overrides.scss @@ -836,4 +836,53 @@ select{ padding: 0; } } +} + +.modal-claim-interest { + .modal-dialog { + width: 70%; + + .table > tbody > tr > td, + .table > tbody > tr > th, + .table > tfoot > tr > td, + .table > tfoot > tr > th, + .table > thead > tr > td, + .table > thead > tr > th { + padding: 8px 30px 8px 0; + } + + .claim-btn { + position: absolute; + right: 29px; + top: 66px; + } + .table-scroll { + height: 366px; + overflow-y: auto; + overflow-x: hidden; + width: 100%; + } + .bold { + font-weight: bold; + } + .green { + color: #66bb6a; + } + .red { + color: #f96868; + } + .locktime { + i { + font-size: 20px; + line-height: 1.1; + } + } + + .refresh-icon { + position: absolute; + right: 20px; + font-size: 20px; + z-index: 100; + } + } } \ No newline at end of file diff --git a/react/src/reducers/dashboard.js b/react/src/reducers/dashboard.js index 98df82e..54e2e63 100644 --- a/react/src/reducers/dashboard.js +++ b/react/src/reducers/dashboard.js @@ -9,14 +9,15 @@ import { VIEW_CACHE_DATA, LOG_GUI_HTTP, TOGGLE_NOTIFICATIONS_MODAL, - DISPLAY_COIND_DOWN_MODAL + DISPLAY_COIND_DOWN_MODAL, + DISPLAY_CLAIM_INTEREST_MODAL } from '../actions/storeType'; const HTTP_STACK_MAX_ENTRIES = 150; // limit stack mem length to N records per type const trimHTTPLogs = (logObject) => { const logObjectArray = Object.keys(logObject); - + if (logObjectArray.length - HTTP_STACK_MAX_ENTRIES === 1) { delete logObject[logObjectArray.shift()]; } @@ -38,6 +39,7 @@ export function Dashboard(state = { }, guiLog: {}, displayCoindDownModal: false, + displayClaimInterestModal: false, }, action) { switch (action.type) { case DASHBOARD_SECTION_CHANGE: @@ -95,7 +97,7 @@ export function Dashboard(state = { const logItem = { [actionTS]: action.log }; newLogState = trimHTTPLogs(Object.assign({}, logState, logItem)); } - + return Object.assign({}, state, { guiLog: newLogState, }); @@ -104,6 +106,11 @@ export function Dashboard(state = { displayCoindDownModal: action.displayCoindDownModal, }); break; + case DISPLAY_CLAIM_INTEREST_MODAL: + return Object.assign({}, state, { + displayClaimInterestModal: action.displayClaimInterestModal, + }); + break; default: return state; }