Browse Source

claim interest modal address dropdown, spv

v0.25
pbca26 7 years ago
parent
commit
7263be0f5a
  1. 54
      react/src/actions/actions/electrum.js
  2. 266
      react/src/components/dashboard/claimInterestModal/claimInterestModal.js
  3. 68
      react/src/components/dashboard/claimInterestModal/claimInterestModal.render.js
  4. 6
      react/src/components/dashboard/claimInterestModal/claimInterestModal.scss
  5. 1
      react/src/components/dashboard/sendCoin/sendCoin.js
  6. 3
      react/src/components/dashboard/walletsData/walletsData.js

54
react/src/actions/actions/electrum.js

@ -233,6 +233,31 @@ export function shepherdElectrumSend(coin, value, sendToAddress, changeAddress)
} }
} }
export function shepherdElectrumSendPromise(coin, value, sendToAddress, changeAddress) {
return new Promise((resolve, reject) => {
return fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx?coin=${coin}&address=${sendToAddress}&value=${value}&change=${changeAddress}&gui=true&push=true&verify=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
})
.catch((error) => {
console.log(error);
dispatch(
triggerToaster(
'shepherdElectrumSendPromise',
'Error',
'error'
)
);
})
.then(response => response.json())
.then(json => {
resolve(json);
});
});
}
export function shepherdElectrumSendPreflight(coin, value, sendToAddress, changeAddress) { export function shepherdElectrumSendPreflight(coin, value, sendToAddress, changeAddress) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx?coin=${coin}&address=${sendToAddress}&value=${value}&change=${changeAddress}&gui=true&push=false&verify=true`, { fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx?coin=${coin}&address=${sendToAddress}&value=${value}&change=${changeAddress}&gui=true&push=false&verify=true`, {
@ -256,4 +281,33 @@ export function shepherdElectrumSendPreflight(coin, value, sendToAddress, change
resolve(json); resolve(json);
}); });
}); });
}
export function shepherdElectrumListunspent(coin, address) {
return new Promise((resolve, reject) => {
fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/listunspent?coin=${coin}&address=${address}&full=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.catch((error) => {
console.log(error);
dispatch(
triggerToaster(
'shepherdElectrumListunspent',
'Error',
'error'
)
);
})
.then(response => response.json())
.then(json => {
if (!json.result) {
resolve('error');
} else {
resolve(json);
}
});
});
} }

266
react/src/components/dashboard/claimInterestModal/claimInterestModal.js

@ -9,6 +9,9 @@ import {
copyString, copyString,
sendToAddressPromise, sendToAddressPromise,
triggerToaster, triggerToaster,
shepherdElectrumListunspent,
shepherdElectrumSendPreflight,
shepherdElectrumSendPromise,
} from '../../../actions/actionCreators'; } from '../../../actions/actionCreators';
import { translate } from '../../../translate/translate'; import { translate } from '../../../translate/translate';
import { import {
@ -27,11 +30,20 @@ class ClaimInterestModal extends React.Component {
transactionsList: [], transactionsList: [],
showZeroInterest: true, showZeroInterest: true,
totalInterest: 0, totalInterest: 0,
spvPreflightSendInProgress: false,
spvVerificationWarning: false,
addressses: {},
addressSelectorOpen: false,
selectedAddress: null,
}; };
this.claimInterestTableRender = this.claimInterestTableRender.bind(this); this.claimInterestTableRender = this.claimInterestTableRender.bind(this);
this.toggleZeroInterest = this.toggleZeroInterest.bind(this); this.toggleZeroInterest = this.toggleZeroInterest.bind(this);
this.loadListUnspent = this.loadListUnspent.bind(this); this.loadListUnspent = this.loadListUnspent.bind(this);
this.checkTransactionsListLength = this.checkTransactionsListLength.bind(this); this.checkTransactionsListLength = this.checkTransactionsListLength.bind(this);
this.cancelClaimInterest = this.cancelClaimInterest.bind(this);
this.openDropMenu = this.openDropMenu.bind(this);
this.closeDropMenu = this.closeDropMenu.bind(this);
this.closeModal = this.closeModal.bind(this);
} }
componentWillMount() { componentWillMount() {
@ -40,25 +52,52 @@ class ClaimInterestModal extends React.Component {
} }
} }
openDropMenu() {
this.setState(Object.assign({}, this.state, {
addressSelectorOpen: !this.state.addressSelectorOpen,
}));
}
closeDropMenu() {
if (this.state.addressSelectorOpen) {
setTimeout(() => {
this.setState(Object.assign({}, this.state, {
addressSelectorOpen: false,
}));
}, 100);
}
}
updateAddressSelection(address) {
this.setState(Object.assign({}, this.state, {
selectedAddress: address,
addressSelectorOpen: !this.state.addressSelectorOpen,
}));
}
loadListUnspent() { loadListUnspent() {
let _transactionsList = []; let _transactionsList = [];
let _totalInterest = 0; let _totalInterest = 0;
getListUnspent(this.props.ActiveCoin.coin) if (this.props.ActiveCoin.mode === 'spv') {
.then((json) => { shepherdElectrumListunspent(
if (json && this.props.ActiveCoin.coin,
json.length) { this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub
for (let i = 0; i < json.length; i++) { ).then((json) => {
getRawTransaction(this.props.ActiveCoin.coin, json[i].txid) if (json !== 'error' &&
.then((_json) => { json.result &&
typeof json.result !== 'string') {
json = json.result;
for (let i = 0; i < json.length; i++) {
_transactionsList.push({ _transactionsList.push({
address: json[i].address, address: json[i].address,
locktime: _json.locktime, locktime: json[i].locktime,
amount: json[i].amount, amount: Number(json[i].amount.toFixed(8)),
interest: json[i].interest, interest: Number(json[i].interest.toFixed(8)),
txid: json[i].txid, txid: json[i].txid,
}); });
_totalInterest += Number(json[i].interest); _totalInterest += Number(json[i].interest.toFixed(8));
if (i === json.length - 1) { if (i === json.length - 1) {
this.setState({ this.setState({
@ -67,39 +106,148 @@ class ClaimInterestModal extends React.Component {
totalInterest: _totalInterest, totalInterest: _totalInterest,
}); });
} }
}
} else {
this.setState({
transactionsList: [],
isLoading: false,
totalInterest: 0,
}); });
} }
});
} else {
getListUnspent(this.props.ActiveCoin.coin)
.then((json) => {
if (json &&
json.length) {
let _addresses = {};
for (let i = 0; i < json.length; i++) {
getRawTransaction(this.props.ActiveCoin.coin, json[i].txid)
.then((_json) => {
_addresses[json[i].address] = json[i].address;
_transactionsList.push({
address: json[i].address,
locktime: _json.locktime,
amount: json[i].amount,
interest: json[i].interest,
txid: json[i].txid,
});
_totalInterest += Number(json[i].interest);
if (i === json.length - 1) {
this.setState({
transactionsList: _transactionsList,
isLoading: false,
totalInterest: _totalInterest,
addressses: _addresses,
selectedAddress: this.state.selectedAddress ? this.state.selectedAddress : _addresses[Object.keys(_addresses)[0]],
});
}
});
}
}
});
}
}
cancelClaimInterest() {
this.setState(Object.assign({}, this.state, {
spvVerificationWarning: false,
spvPreflightSendInProgress: false,
}));
}
confirmClaimInterest() {
shepherdElectrumSendPromise(
this.props.ActiveCoin.coin,
this.props.ActiveCoin.balance.balanceSats,
this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub,
this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub
).then((res) => {
if (res.msg === 'error') {
Store.dispatch(
triggerToaster(
res.result,
'Error',
'error'
)
);
} else {
Store.dispatch(
triggerToaster(
`${translate('TOASTR.CLAIM_INTEREST_BALANCE_SENT_P1')} ${this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub}. ${translate('TOASTR.CLAIM_INTEREST_BALANCE_SENT_P2')}`,
translate('TOASTR.WALLET_NOTIFICATION'),
'success',
false
)
);
this.setState({
transactionsList: [],
isLoading: false,
totalInterest: 0,
});
} }
}); });
} }
claimInterest(address, amount) { claimInterest(address, amount) {
if (this.props.ActiveCoin.coin === 'KMD') { if (this.props.ActiveCoin.coin === 'KMD') {
sendToAddressPromise( if (this.props.ActiveCoin.mode === 'spv') {
this.props.ActiveCoin.coin, this.setState(Object.assign({}, this.state, {
this.state.transactionsList[0].address, spvVerificationWarning: false,
this.props.ActiveCoin.balance.transparent spvPreflightSendInProgress: true,
).then((json) => { }));
if (json.error &&
json.error.code) { shepherdElectrumSendPreflight(
Store.dispatch( this.props.ActiveCoin.coin,
triggerToaster( this.props.ActiveCoin.balance.balanceSats,
json.error.message, this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub,
'Error', this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub
'error' ).then((sendPreflight) => {
) if (sendPreflight &&
); sendPreflight.msg === 'success') {
} else if (json.result && json.result.length && json.result.length === 64) { this.setState(Object.assign({}, this.state, {
Store.dispatch( spvVerificationWarning: !sendPreflight.result.utxoVerified,
triggerToaster( spvPreflightSendInProgress: false,
`${translate('TOASTR.CLAIM_INTEREST_BALANCE_SENT_P1')} ${this.state.transactionsList[0].address}. ${translate('TOASTR.CLAIM_INTEREST_BALANCE_SENT_P2')}`, }));
translate('TOASTR.WALLET_NOTIFICATION'),
'success', if (sendPreflight.result.utxoVerified) {
false this.confirmClaimInterest();
) }
); } else {
} this.setState(Object.assign({}, this.state, {
}); spvPreflightSendInProgress: false,
}));
}
});
} else {
sendToAddressPromise(
this.props.ActiveCoin.coin,
this.state.selectedAddress, // 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(
`${translate('TOASTR.CLAIM_INTEREST_BALANCE_SENT_P1')} ${this.state.transactionsList[0].address}. ${translate('TOASTR.CLAIM_INTEREST_BALANCE_SENT_P2')}`,
translate('TOASTR.WALLET_NOTIFICATION'),
'success',
false
)
);
}
});
}
} }
} }
@ -126,6 +274,46 @@ class ClaimInterestModal extends React.Component {
return _ClaimInterestTableRender.call(this); return _ClaimInterestTableRender.call(this);
} }
addressDropdownRender() {
let _items = [];
for (let key in this.state.addressses) {
_items.push(
<li
className="selected"
key={ key }>
<a onClick={ () => this.updateAddressSelection(key) }>
<span className="text">{ key }</span>
<span
className="glyphicon glyphicon-ok check-mark pull-right"
style={{ display: this.state.selectedAddress === key ? 'inline-block' : 'none' }}></span>
</a>
</li>
);
}
return (
<div className={ `btn-group bootstrap-select form-control form-material showkmdwalletaddrs show-tick ${(this.state.addressSelectorOpen ? 'open' : '')}` }>
<button
type="button"
className={ 'btn dropdown-toggle btn-info' + (this.props.ActiveCoin.mode === 'spv' ? ' disabled' : '') }
onClick={ this.openDropMenu }>
<span className="filter-option pull-left">{ this.state.selectedAddress }</span>
<span
className="bs-caret"
style={{ display: 'inline-block' }}>
<span className="caret"></span>
</span>
</button>
<div className="dropdown-menu open">
<ul className="dropdown-menu inner">
{ _items }
</ul>
</div>
</div>
);
}
componentWillReceiveProps(props) { componentWillReceiveProps(props) {
if (props.Dashboard.displayClaimInterestModal !== this.state.open) { if (props.Dashboard.displayClaimInterestModal !== this.state.open) {
this.setState({ this.setState({
@ -140,6 +328,10 @@ class ClaimInterestModal extends React.Component {
} }
closeModal() { closeModal() {
this.setState({
addressses: {},
selectedAddress: null,
});
Store.dispatch(toggleClaimInterestModal(false)); Store.dispatch(toggleClaimInterestModal(false));
} }
@ -157,12 +349,14 @@ class ClaimInterestModal extends React.Component {
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
return { return {
ActiveCoin: { ActiveCoin: {
mode: state.ActiveCoin.mode,
coin: state.ActiveCoin.coin, coin: state.ActiveCoin.coin,
balance: state.ActiveCoin.balance, balance: state.ActiveCoin.balance,
activeSection: state.ActiveCoin.activeSection, activeSection: state.ActiveCoin.activeSection,
}, },
Dashboard: { Dashboard: {
displayClaimInterestModal: state.Dashboard.displayClaimInterestModal, displayClaimInterestModal: state.Dashboard.displayClaimInterestModal,
electrumCoins: state.Dashboard.electrumCoins,
}, },
}; };
}; };

68
react/src/components/dashboard/claimInterestModal/claimInterestModal.render.js

@ -23,10 +23,14 @@ export const _ClaimInterestTableRender = function() {
<td>{ _transactionsList[i].interest }</td> <td>{ _transactionsList[i].interest }</td>
<td className="locktime center"> <td className="locktime center">
{ _transactionsList[i].locktime && { _transactionsList[i].locktime &&
<i className="fa-check-circle green"></i> <i
title={ _transactionsList[i].locktime }
className="fa-check-circle green"></i>
} }
{ !_transactionsList[i].locktime && { !_transactionsList[i].locktime &&
<i className="fa-exclamation-circle red"></i> <i
title={ _transactionsList[i].locktime }
className="fa-exclamation-circle red"></i>
} }
</td> </td>
</tr> </tr>
@ -59,12 +63,52 @@ export const _ClaimInterestTableRender = function() {
onClick={ this.toggleZeroInterest }> onClick={ this.toggleZeroInterest }>
{ translate('CLAIM_INTEREST.SHOW_ZERO_INTEREST') } { translate('CLAIM_INTEREST.SHOW_ZERO_INTEREST') }
</div> </div>
<button { !this.state.spvVerificationWarning &&
type="button" <button
className="btn btn-success waves-effect waves-light claim-btn" type="button"
onClick={ () => this.claimInterest() }> className="btn btn-success waves-effect waves-light claim-btn"
<i className="icon fa-dollar"></i> { translate('CLAIM_INTEREST.CLAIM_INTEREST', `${this.state.totalInterest} KMD `) } onClick={ () => this.claimInterest() }
</button> disabled={ this.state.spvPreflightSendInProgress }>
{ !this.state.spvPreflightSendInProgress &&
<i className="icon fa-dollar margin-right-5"></i>
}
{ !this.state.spvPreflightSendInProgress &&
<span>{ translate('CLAIM_INTEREST.CLAIM_INTEREST', `${this.state.totalInterest} KMD `) }</span>
}
{ this.state.spvPreflightSendInProgress &&
<span>{ translate('SEND.SPV_VERIFYING') }...</span>
}
</button>
}
{ this.state.spvVerificationWarning &&
<div
className="padding-top-20"
style={{ fontSize: '15px' }}>
<strong className="color-warning">{ translate('SEND.WARNING') }:</strong> { translate('SEND.WARNING_SPV_P1') } { translate('SEND.WARNING_SPV_P2') }
<div className="margin-top-15">
<button
type="button"
className="btn btn-primary"
onClick={ this.confirmClaimInterest }>
{ translate('INDEX.CONFIRM') }
</button>
<button
type="button"
className="btn btn-primary margin-left-15"
onClick={ this.cancelClaimInterest }>
Cancel
</button>
</div>
</div>
}
{ this.props.ActiveCoin.mode === 'native' &&
this.state.addressses &&
Object.keys(this.state.addressses).length > 0 &&
<div className="margin-top-20 margin-bottom-20">
<div className="margin-bottom-5">Send my balance to</div>
{ this.addressDropdownRender() }
</div>
}
</div> </div>
} }
<div className="table-scroll"> <div className="table-scroll">
@ -98,7 +142,7 @@ export const _ClaimInterestTableRender = function() {
export const ClaimInterestModalRender = function() { export const ClaimInterestModalRender = function() {
return ( return (
<span> <span onClick={ this.closeDropMenu }>
<div className={ 'modal modal-claim-interest modal-3d-sign ' + (this.state.open ? 'show in' : 'fade hide') }> <div className={ 'modal modal-claim-interest modal-3d-sign ' + (this.state.open ? 'show in' : 'fade hide') }>
<div className="modal-dialog modal-center modal-sm"> <div className="modal-dialog modal-center modal-sm">
<div className="modal-content"> <div className="modal-content">
@ -120,10 +164,12 @@ export const ClaimInterestModalRender = function() {
{ this.state.isLoading && { this.state.isLoading &&
<span>{ translate('INDEX.LOADING') }...</span> <span>{ translate('INDEX.LOADING') }...</span>
} }
{ !this.state.isLoading && this.checkTransactionsListLength() && { !this.state.isLoading &&
this.checkTransactionsListLength() &&
<div>{ this.claimInterestTableRender() }</div> <div>{ this.claimInterestTableRender() }</div>
} }
{ !this.state.isLoading && !this.checkTransactionsListLength() && { !this.state.isLoading &&
!this.checkTransactionsListLength() &&
<div>{ translate('INDEX.NO_DATA') }</div> <div>{ translate('INDEX.NO_DATA') }</div>
} }
</div> </div>

6
react/src/components/dashboard/claimInterestModal/claimInterestModal.scss

@ -16,7 +16,7 @@
margin-bottom: 30px; margin-bottom: 30px;
} }
.table-scroll { .table-scroll {
height: 366px; max-height: 366px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
width: 100%; width: 100%;
@ -43,5 +43,9 @@
font-size: 20px; font-size: 20px;
z-index: 100; z-index: 100;
} }
.bootstrap-select {
max-width: 400px;
}
} }
} }

1
react/src/components/dashboard/sendCoin/sendCoin.js

@ -23,6 +23,7 @@ import { isPositiveNumber } from '../../../util/number';
// TODO: - add links to explorers // TODO: - add links to explorers
// - render z address trim // - render z address trim
// - handle click outside
class SendCoin extends React.Component { class SendCoin extends React.Component {
constructor(props) { constructor(props) {

3
react/src/components/dashboard/walletsData/walletsData.js

@ -88,12 +88,11 @@ class WalletsData extends React.Component {
displayClaimInterestUI() { displayClaimInterestUI() {
if (this.props.ActiveCoin && if (this.props.ActiveCoin &&
this.props.ActiveCoin.coin === 'KMD' && this.props.ActiveCoin.coin === 'KMD' &&
this.props.ActiveCoin.mode === 'native' &&
this.props.ActiveCoin.balance) { this.props.ActiveCoin.balance) {
if (this.props.ActiveCoin.balance.interest && if (this.props.ActiveCoin.balance.interest &&
this.props.ActiveCoin.balance.interest > 0) { this.props.ActiveCoin.balance.interest > 0) {
return 777; return 777;
} else if (this.props.ActiveCoin.balance.transparent && this.props.ActiveCoin.balance.transparent >= 10) { } else if ((this.props.ActiveCoin.balance.transparent && this.props.ActiveCoin.balance.transparent >= 10) || (this.props.ActiveCoin.balance.balance && this.props.ActiveCoin.balance.balance >= 10)) {
return -777; return -777;
} }
} }

Loading…
Cancel
Save