pbca26
7 years ago
15 changed files with 912 additions and 10 deletions
@ -0,0 +1,168 @@ |
|||||
|
import { translate } from '../../translate/translate'; |
||||
|
import Config from '../../config'; |
||||
|
import { triggerToaster } from '../actionCreators'; |
||||
|
import Store from '../../store'; |
||||
|
|
||||
|
export function shepherdElectionsBalance(coin, address) { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/getbalance?coin=${coin}&address=${address}&token=${Config.token}`, { |
||||
|
method: 'GET', |
||||
|
headers: { |
||||
|
'Content-Type': 'application/json', |
||||
|
}, |
||||
|
}) |
||||
|
.catch((error) => { |
||||
|
console.log(error); |
||||
|
dispatch( |
||||
|
triggerToaster( |
||||
|
'shepherdElectionsBalance', |
||||
|
'Error', |
||||
|
'error' |
||||
|
) |
||||
|
); |
||||
|
}) |
||||
|
.then(response => response.json()) |
||||
|
.then(json => { |
||||
|
resolve(!json.result ? 'error' : json); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function shepherdElectionsTransactions(coin, address, type) { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/elections/listtransactions?coin=${coin}&address=${address}&full=true&type=${type}&maxlength=20&token=${Config.token}`, { |
||||
|
method: 'GET', |
||||
|
headers: { |
||||
|
'Content-Type': 'application/json', |
||||
|
}, |
||||
|
}) |
||||
|
.catch((error) => { |
||||
|
console.log(error); |
||||
|
dispatch( |
||||
|
triggerToaster( |
||||
|
'shepherdElectionsTransactions', |
||||
|
'Error', |
||||
|
'error' |
||||
|
) |
||||
|
); |
||||
|
}) |
||||
|
.then(response => response.json()) |
||||
|
.then(json => { |
||||
|
resolve(!json.result ? 'error' : json); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function shepherdElectionsStatus() { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
return fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/elections/status`, { |
||||
|
method: 'POST', |
||||
|
headers: { |
||||
|
'Content-Type': 'application/json', |
||||
|
}, |
||||
|
body: JSON.stringify({ |
||||
|
token: Config.token, |
||||
|
}), |
||||
|
}) |
||||
|
.catch((error) => { |
||||
|
console.log(error); |
||||
|
Store.dispatch( |
||||
|
triggerToaster( |
||||
|
'shepherdElectionsStatus', |
||||
|
'Error', |
||||
|
'error' |
||||
|
) |
||||
|
); |
||||
|
}) |
||||
|
.then(response => response.json()) |
||||
|
.then(json => { |
||||
|
resolve(json); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function shepherdElectionsLogin(seed, network) { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
return fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/elections/login`, { |
||||
|
method: 'POST', |
||||
|
headers: { |
||||
|
'Content-Type': 'application/json', |
||||
|
}, |
||||
|
body: JSON.stringify({ |
||||
|
seed, |
||||
|
network, |
||||
|
iguana: true, |
||||
|
token: Config.token, |
||||
|
}), |
||||
|
}) |
||||
|
.catch((error) => { |
||||
|
console.log(error); |
||||
|
Store.dispatch( |
||||
|
triggerToaster( |
||||
|
'shepherdElectionsLogin', |
||||
|
'Error', |
||||
|
'error' |
||||
|
) |
||||
|
); |
||||
|
}) |
||||
|
.then(response => response.json()) |
||||
|
.then(json => { |
||||
|
resolve(json); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function shepherdElectionsLogout() { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
return fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/elections/logout`, { |
||||
|
method: 'POST', |
||||
|
headers: { |
||||
|
'Content-Type': 'application/json', |
||||
|
}, |
||||
|
body: JSON.stringify({ |
||||
|
token: Config.token, |
||||
|
}), |
||||
|
}) |
||||
|
.catch((error) => { |
||||
|
console.log(error); |
||||
|
Store.dispatch( |
||||
|
triggerToaster( |
||||
|
'shepherdElectionsLogout', |
||||
|
'Error', |
||||
|
'error' |
||||
|
) |
||||
|
); |
||||
|
}) |
||||
|
.then(response => response.json()) |
||||
|
.then(json => { |
||||
|
resolve(json); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function shepherdElectionsSend(coin, value, sendToAddress, changeAddress, opreturn) { |
||||
|
value = Math.floor(value); |
||||
|
|
||||
|
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}&vote=true&push=true&verify=false&opreturn=${opreturn}&token=${Config.token}`, { |
||||
|
method: 'GET', |
||||
|
headers: { |
||||
|
'Content-Type': 'application/json', |
||||
|
}, |
||||
|
}) |
||||
|
.catch((error) => { |
||||
|
console.log(error); |
||||
|
Store.dispatch( |
||||
|
triggerToaster( |
||||
|
'shepherdElectionsSend', |
||||
|
'Error', |
||||
|
'error' |
||||
|
) |
||||
|
); |
||||
|
}) |
||||
|
.then(response => response.json()) |
||||
|
.then(json => { |
||||
|
resolve(json); |
||||
|
}); |
||||
|
}); |
||||
|
} |
After Width: | Height: | Size: 7.7 KiB |
@ -0,0 +1,543 @@ |
|||||
|
import React from 'react'; |
||||
|
import { connect } from 'react-redux'; |
||||
|
import { translate } from '../../../translate/translate'; |
||||
|
import { |
||||
|
toggleNotaryElectionsModal, |
||||
|
shepherdElectionsSend, |
||||
|
shepherdElectionsLogout, |
||||
|
shepherdElectionsLogin, |
||||
|
shepherdElectionsStatus, |
||||
|
triggerToaster, |
||||
|
shepherdElectionsBalance, |
||||
|
shepherdElectionsTransactions, |
||||
|
} from '../../../actions/actionCreators'; |
||||
|
import Store from '../../../store'; |
||||
|
import { secondsToString } from '../../../util/time'; |
||||
|
import { isPositiveNumber } from '../../../util/number'; |
||||
|
import mainWindow from '../../../util/mainWindow'; |
||||
|
import Spinner from '../spinner/spinner'; |
||||
|
|
||||
|
const ELECTIONS_SYNC_UPDATE_INTERVAL = 120000; // every 2 min
|
||||
|
|
||||
|
class NotaryElectionsModal extends React.Component { |
||||
|
constructor(props) { |
||||
|
super(props); |
||||
|
this.state = { |
||||
|
loginPassphrase: '', |
||||
|
seedInputVisibility: false, |
||||
|
userType: 'voter', |
||||
|
region: null, |
||||
|
trimPassphraseTimer: null, |
||||
|
isAuth: false, |
||||
|
balance: 0, |
||||
|
transactions: null, |
||||
|
coin: mainWindow.nnVoteChain, |
||||
|
pub: null, |
||||
|
amount: 0, |
||||
|
address: '', |
||||
|
}; |
||||
|
this.defaultState = JSON.parse(JSON.stringify(this.state)); |
||||
|
this.closeModal = this.closeModal.bind(this); |
||||
|
this.toggleSeedInputVisibility = this.toggleSeedInputVisibility.bind(this); |
||||
|
this.resizeLoginTextarea = this.resizeLoginTextarea.bind(this); |
||||
|
this.updateLoginPassPhraseInput = this.updateLoginPassPhraseInput.bind(this); |
||||
|
this.setUserType = this.setUserType.bind(this); |
||||
|
this.setRegion = this.setRegion.bind(this); |
||||
|
this.loginSeed = this.loginSeed.bind(this); |
||||
|
this.logout = this.logout.bind(this); |
||||
|
this.sync = this.sync.bind(this); |
||||
|
this.send = this.send.bind(this); |
||||
|
this.updateInput = this.updateInput.bind(this); |
||||
|
this.sendValidate = this.sendValidate.bind(this); |
||||
|
this.electionsDataInterval = null; |
||||
|
} |
||||
|
|
||||
|
sendValidate() { |
||||
|
let valid = true; |
||||
|
|
||||
|
const _amount = this.state.amount; |
||||
|
const _balance = this.state.balance; |
||||
|
const _fee = 0.0001; |
||||
|
|
||||
|
if (Number(_amount) + _fee > _balance) { |
||||
|
Store.dispatch( |
||||
|
triggerToaster( |
||||
|
`${translate('SEND.INSUFFICIENT_FUNDS')} ${translate('SEND.MAX_AVAIL_BALANCE')} ${Number(_balance - _fee)} ${this.state.coin}`, |
||||
|
translate('TOASTR.WALLET_NOTIFICATION'), |
||||
|
'error' |
||||
|
) |
||||
|
); |
||||
|
valid = false; |
||||
|
} else if (Number(_amount) < _fee) { |
||||
|
Store.dispatch( |
||||
|
triggerToaster( |
||||
|
`${translate('SEND.AMOUNT_IS_TOO_SMALL', this.state.amount)}, ${translate('SEND.MIN_AMOUNT_IS', this.state.coin)} ${_fee}`, |
||||
|
translate('TOASTR.WALLET_NOTIFICATION'), |
||||
|
'error' |
||||
|
) |
||||
|
); |
||||
|
valid = false; |
||||
|
} |
||||
|
|
||||
|
if (!this.state.address || |
||||
|
this.state.address.length < 34) { |
||||
|
Store.dispatch( |
||||
|
triggerToaster( |
||||
|
'Wrong address format', |
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
return valid; |
||||
|
} |
||||
|
|
||||
|
send() { |
||||
|
if (this.sendValidate()) { |
||||
|
shepherdElectionsSend( |
||||
|
this.state.coin, |
||||
|
(this.state.amount * 100000000) - 10000, |
||||
|
this.state.address, |
||||
|
this.state.pub, |
||||
|
'ne2k18-' + this.state.region, |
||||
|
) |
||||
|
.then((res) => { |
||||
|
if (res.msg === 'success') { |
||||
|
Store.dispatch( |
||||
|
triggerToaster( |
||||
|
`You succesfully voted ${this.state.amount} for ${this.state.address}`, |
||||
|
'Notary voting 2018', |
||||
|
'success', |
||||
|
false |
||||
|
) |
||||
|
); |
||||
|
let _transactions = this.state.transactions; |
||||
|
_transactions.unshift({ |
||||
|
address: this.state.address, |
||||
|
amount: this.state.amount - 0.0001, |
||||
|
region: 'ne2k18-' + this.state.region, |
||||
|
timestamp: Math.floor(Date.now() / 1000), |
||||
|
}); |
||||
|
this.setState({ |
||||
|
transactions: _transactions, |
||||
|
}); |
||||
|
} else { |
||||
|
Store.dispatch( |
||||
|
triggerToaster( |
||||
|
res.result.txid || res.result, |
||||
|
'Notary voting 2018', |
||||
|
'error' |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
sync() { |
||||
|
shepherdElectionsStatus() |
||||
|
.then((res) => { |
||||
|
if (res.result !== 'unauth') { |
||||
|
shepherdElectionsBalance(this.state.coin, res.result) |
||||
|
.then((res) => { |
||||
|
if (res.msg === 'success') { |
||||
|
this.setState({ |
||||
|
balance: res.result.balance, |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
shepherdElectionsTransactions(this.state.coin, res.result, this.state.userType) |
||||
|
.then((res) => { |
||||
|
this.setState({ |
||||
|
transactions: res.result, |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
this.setState({ |
||||
|
isAuth: true, |
||||
|
pub: res.result, |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
componentWillReceiveProps(props) { |
||||
|
if (props && |
||||
|
props.Main && |
||||
|
props.Main.displayNotaryElectionsModal && |
||||
|
!this.electionsDataInterval) { |
||||
|
this.sync(); |
||||
|
this.electionsDataInterval = setInterval(() => { |
||||
|
this.sync(); |
||||
|
}, ELECTIONS_SYNC_UPDATE_INTERVAL); |
||||
|
} else { |
||||
|
clearInterval(this.electionsDataInterval); |
||||
|
this.electionsDataInterval = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
updateInput(e) { |
||||
|
this.setState({ |
||||
|
[e.target.name]: e.target.value, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
handleKeydown(e) { |
||||
|
this.updateLoginPassPhraseInput(e); |
||||
|
|
||||
|
if (e.key === 'Enter') { |
||||
|
this.loginSeed(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
setRegion(region) { |
||||
|
this.setState({ |
||||
|
region, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
setUserType(type) { |
||||
|
this.setState({ |
||||
|
userType: type, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
toggleSeedInputVisibility() { |
||||
|
this.setState({ |
||||
|
seedInputVisibility: !this.state.seedInputVisibility, |
||||
|
}); |
||||
|
|
||||
|
this.resizeLoginTextarea(); |
||||
|
} |
||||
|
|
||||
|
resizeLoginTextarea() { |
||||
|
// auto-size textarea
|
||||
|
setTimeout(() => { |
||||
|
if (this.state.seedInputVisibility) { |
||||
|
document.querySelector('#loginPassphrase').style.height = '1px'; |
||||
|
document.querySelector('#loginPassphrase').style.height = `${(15 + document.querySelector('#loginPassphrase').scrollHeight)}px`; |
||||
|
} |
||||
|
}, 100); |
||||
|
} |
||||
|
|
||||
|
updateLoginPassPhraseInput(e) { |
||||
|
// remove any empty chars from the start/end of the string
|
||||
|
const newValue = e.target.value; |
||||
|
|
||||
|
clearTimeout(this.state.trimPassphraseTimer); |
||||
|
|
||||
|
const _trimPassphraseTimer = setTimeout(() => { |
||||
|
this.setState({ |
||||
|
loginPassphrase: newValue ? newValue.trim() : '', // hardcoded field name
|
||||
|
}); |
||||
|
}, 2000); |
||||
|
|
||||
|
this.resizeLoginTextarea(); |
||||
|
|
||||
|
this.setState({ |
||||
|
trimPassphraseTimer: _trimPassphraseTimer, |
||||
|
[e.target.name === 'loginPassphraseTextarea' ? 'loginPassphrase' : e.target.name]: newValue, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
loginSeed() { |
||||
|
shepherdElectionsLogin(this.state.loginPassphrase, this.state.coin) |
||||
|
.then((res) => { |
||||
|
if (res.msg === 'success') { |
||||
|
this.setState({ |
||||
|
isAuth: true, |
||||
|
pub: res.result, |
||||
|
}); |
||||
|
} else { |
||||
|
Store.dispatch( |
||||
|
triggerToaster( |
||||
|
'loginSeed', |
||||
|
'Error', |
||||
|
'error' |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
this.setState({ |
||||
|
loginPassphrase: '', |
||||
|
loginPassPhraseSeedType: null, |
||||
|
}); |
||||
|
|
||||
|
// reset login input vals
|
||||
|
this.refs.loginPassphrase.value = ''; |
||||
|
this.refs.loginPassphraseTextarea.value = ''; |
||||
|
|
||||
|
this.sync(); |
||||
|
} |
||||
|
|
||||
|
logout() { |
||||
|
shepherdElectionsLogout() |
||||
|
.then((res) => { |
||||
|
this.setState(this.defaultState); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
closeModal() { |
||||
|
clearInterval(this.electionsDataInterval); |
||||
|
this.electionsDataInterval = null; |
||||
|
Store.dispatch(toggleNotaryElectionsModal(false)); |
||||
|
} |
||||
|
|
||||
|
displayTxHistoryRender() { |
||||
|
if (this.state.transactions && |
||||
|
this.state.transactions.length) { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
renderHistoryRegion(region) { |
||||
|
let _region; |
||||
|
|
||||
|
switch (region) { |
||||
|
case 'ne2k18-sh': |
||||
|
_region = 'SH'; |
||||
|
break; |
||||
|
case 'ne2k18-na': |
||||
|
_region = 'NA'; |
||||
|
break; |
||||
|
case 'ne2k18-ae': |
||||
|
_region = 'AE'; |
||||
|
break; |
||||
|
case 'ne2k18-eu': |
||||
|
_region = 'EU'; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return _region; |
||||
|
} |
||||
|
|
||||
|
renderHistory() { |
||||
|
const _history = this.state.transactions; |
||||
|
let _items = []; |
||||
|
|
||||
|
for (let i = 0; i < _history.length; i++) { |
||||
|
_items.push( |
||||
|
<tr key={ `notary-elections-history-${i}` }> |
||||
|
<td>{ _history[i].address }</td> |
||||
|
<td>{ Number(_history[i].amount) }</td> |
||||
|
<td>{ this.renderHistoryRegion(_history[i].region) }</td> |
||||
|
<td>{ secondsToString(_history[i].timestamp) }</td> |
||||
|
</tr> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<table className="table table-hover dataTable table-striped"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th>{ this.state.userType === 'voter' ? 'To' : 'From' }</th> |
||||
|
<th>{ translate('INDEX.AMOUNT') }</th> |
||||
|
<th>Region</th> |
||||
|
<th>Time</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
{ _items } |
||||
|
</tbody> |
||||
|
<tfoot> |
||||
|
<tr> |
||||
|
<th>{ this.state.userType === 'voter' ? 'To' : 'From' }</th> |
||||
|
<th>{ translate('INDEX.AMOUNT') }</th> |
||||
|
<th>Region</th> |
||||
|
<th>Time</th> |
||||
|
</tr> |
||||
|
</tfoot> |
||||
|
</table> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
if (this.props && |
||||
|
this.props.Main && |
||||
|
this.props.Main.displayNotaryElectionsModal) { |
||||
|
return ( |
||||
|
<div> |
||||
|
<div className="modal show notary-elections-modal ff"> |
||||
|
<div |
||||
|
onClick={ this.closeModal } |
||||
|
className="modal-close-overlay"></div> |
||||
|
<div className="modal-dialog modal-center modal-lg"> |
||||
|
<div |
||||
|
onClick={ this.closeModal } |
||||
|
className="modal-close-overlay"></div> |
||||
|
<div className="modal-content"> |
||||
|
<div className="modal-body modal-body-container"> |
||||
|
<div className="modal-resizable"> |
||||
|
<div className="elections-title-bar"> |
||||
|
<img src={ `assets/images/native/kmd_header_title_logo.png` } /> |
||||
|
<div className="elections-title">Notary Elections 2018</div> |
||||
|
</div> |
||||
|
{ this.state.isAuth && |
||||
|
<button |
||||
|
onClick={ this.logout } |
||||
|
className="btn btn-md btn-info btn-block ladda-button elections-logout-btn"> |
||||
|
<i className="fa fa-power-off margin-right-5"></i>Logout |
||||
|
</button> |
||||
|
} |
||||
|
<div className="elections-user-type"> |
||||
|
<a |
||||
|
className={ this.state.userType === 'voter' ? 'active' : '' } |
||||
|
onClick={ () => this.setUserType('voter') }><i className="fa fa-file margin-right-10"></i>I'm a voter</a> |
||||
|
<span className="margin-left-30 margin-right-30">|</span> |
||||
|
<a |
||||
|
className={ this.state.userType === 'candidate' ? 'active' : '' } |
||||
|
onClick={ () => this.setUserType('candidate') }><i className="fa fa-user margin-right-10"></i>I'm a candidate</a> |
||||
|
</div> |
||||
|
{ !this.state.isAuth && |
||||
|
<div className="elections-login"> |
||||
|
<label |
||||
|
className="floating-label" |
||||
|
htmlFor="inputPassword">{ translate('INDEX.WALLET_SEED') }</label> |
||||
|
<input |
||||
|
type="password" |
||||
|
name="loginPassphrase" |
||||
|
ref="loginPassphrase" |
||||
|
className={ !this.state.seedInputVisibility ? 'form-control' : 'hide' } |
||||
|
onChange={ this.updateLoginPassPhraseInput } |
||||
|
onKeyDown={ (event) => this.handleKeydown(event) } |
||||
|
autoComplete="off" |
||||
|
value={ this.state.loginPassphrase || '' } /> |
||||
|
<textarea |
||||
|
className={ this.state.seedInputVisibility ? 'form-control' : 'hide' } |
||||
|
id="loginPassphrase" |
||||
|
ref="loginPassphraseTextarea" |
||||
|
name="loginPassphraseTextarea" |
||||
|
autoComplete="off" |
||||
|
onChange={ this.updateLoginPassPhraseInput } |
||||
|
onKeyDown={ (event) => this.handleKeydown(event) } |
||||
|
value={ this.state.loginPassphrase || '' }></textarea> |
||||
|
<i |
||||
|
className={ 'seed-toggle fa fa-eye' + (!this.state.seedInputVisibility ? '-slash' : '') } |
||||
|
onClick={ this.toggleSeedInputVisibility }></i> |
||||
|
<button |
||||
|
onClick={ this.loginSeed } |
||||
|
disabled={ !this.state.loginPassphrase || !this.state.loginPassphrase.length } |
||||
|
className="btn btn-md btn-primary btn-block ladda-button elections-login-btn"> |
||||
|
Login |
||||
|
</button> |
||||
|
</div> |
||||
|
} |
||||
|
{ this.state.isAuth && |
||||
|
!this.state.transactions && |
||||
|
!this.state.balance && |
||||
|
<Spinner /> |
||||
|
} |
||||
|
{ this.state.isAuth && |
||||
|
this.state.userType === 'voter' && |
||||
|
<div className="elections-voter-ui"> |
||||
|
<div className="elections-map"> |
||||
|
<img src={ `assets/images/world-map.png` } /> |
||||
|
<div className={ 'elections-map-node elections-map-node--na' + (this.state.region === 'na' ? ' active' : '') }> |
||||
|
<label className="notary-elections-node-title">NA</label> |
||||
|
<img |
||||
|
onClick={ () => this.setRegion('na') } |
||||
|
src={ `assets/images/cryptologo/kmd.png` } /> |
||||
|
</div> |
||||
|
<div className={ 'elections-map-node elections-map-node--sh' + (this.state.region === 'sh' ? ' active' : '') }> |
||||
|
<label className="notary-elections-node-title">SH</label> |
||||
|
<img |
||||
|
onClick={ () => this.setRegion('sh') } |
||||
|
src={ `assets/images/cryptologo/kmd.png` } /> |
||||
|
</div> |
||||
|
<div className={ 'elections-map-node elections-map-node--ae' + (this.state.region === 'ae' ? ' active' : '') }> |
||||
|
<label className="notary-elections-node-title">AE</label> |
||||
|
<img |
||||
|
onClick={ () => this.setRegion('ae') } |
||||
|
src={ `assets/images/cryptologo/kmd.png` } /> |
||||
|
</div> |
||||
|
<div className={ 'elections-map-node elections-map-node--eu' + (this.state.region === 'eu' ? ' active' : '') }> |
||||
|
<label className="notary-elections-node-title">EU</label> |
||||
|
<img |
||||
|
onClick={ () => this.setRegion('eu') } |
||||
|
src={ `assets/images/cryptologo/kmd.png` } /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
} |
||||
|
{ this.state.isAuth && |
||||
|
<div className={ `elections-balance` + (this.state.userType === 'candidate' ? ' margin-top-25' : '') }> |
||||
|
You have <strong>{ this.state.balance }</strong> VOTE |
||||
|
</div> |
||||
|
} |
||||
|
{ this.state.isAuth && |
||||
|
this.state.userType === 'voter' && |
||||
|
<div className="elections-send margin-top-10"> |
||||
|
<input |
||||
|
type="text" |
||||
|
className="form-control block margin-bottom-10" |
||||
|
name="address" |
||||
|
value={ this.state.address !== 0 ? this.state.address : '' } |
||||
|
onChange={ this.updateInput } |
||||
|
placeholder="Enter an address" |
||||
|
autoComplete="off" /> |
||||
|
<input |
||||
|
type="text" |
||||
|
className="form-control inline" |
||||
|
name="amount" |
||||
|
value={ this.state.amount !== 0 ? this.state.amount : '' } |
||||
|
onChange={ this.updateInput } |
||||
|
placeholder="Enter an amout, e.g. 1" |
||||
|
autoComplete="off" /> |
||||
|
<button |
||||
|
onClick={ this.send } |
||||
|
disabled={ !this.state.amount || !this.state.address || !this.state.address.length || !this.state.region || this.state.address === this.state.pub } |
||||
|
className="btn btn-md btn-primary btn-block ladda-button elections-login-btn"> |
||||
|
Vote |
||||
|
</button> |
||||
|
</div> |
||||
|
} |
||||
|
{ this.displayTxHistoryRender() && |
||||
|
<div> |
||||
|
<div className={ `elections-history` + (this.state.userType === 'voter' ? ' margin-top-20' : '') }> |
||||
|
History |
||||
|
</div> |
||||
|
{ this.renderHistory() } |
||||
|
</div> |
||||
|
} |
||||
|
</div> |
||||
|
</div> |
||||
|
<div className="modal-footer"> |
||||
|
<button |
||||
|
type="button" |
||||
|
className="btn btn-default" |
||||
|
onClick={ this.closeModal }>{ translate('INDEX.CLOSE') }</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div className="modal-backdrop show in"></div> |
||||
|
</div> |
||||
|
); |
||||
|
} else { |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const mapStateToProps = (state) => { |
||||
|
return { |
||||
|
Main: state.Main, |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
export default connect(mapStateToProps)(NotaryElectionsModal); |
@ -0,0 +1,142 @@ |
|||||
|
.notary-elections-modal { |
||||
|
color: #757575 !important; |
||||
|
|
||||
|
label, |
||||
|
input { |
||||
|
color: #757575 !important; |
||||
|
} |
||||
|
|
||||
|
.loader { |
||||
|
float: none; |
||||
|
} |
||||
|
|
||||
|
.modal-body-container { |
||||
|
height: inherit; |
||||
|
max-height: 90vh; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
|
||||
|
.elections-title { |
||||
|
font-size: 18px; |
||||
|
margin-left: 83px; |
||||
|
} |
||||
|
|
||||
|
.elections-user-type { |
||||
|
text-align: center; |
||||
|
margin-top: 40px; |
||||
|
|
||||
|
a { |
||||
|
font-size: 18px; |
||||
|
color: #2f6363; |
||||
|
cursor: pointer; |
||||
|
|
||||
|
&.active { |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.elections-logout-btn { |
||||
|
width: 100px; |
||||
|
position: absolute; |
||||
|
top: 20px; |
||||
|
right: 20px; |
||||
|
} |
||||
|
|
||||
|
.elections-login { |
||||
|
margin-top: 15px; |
||||
|
} |
||||
|
|
||||
|
.elections-login-btn { |
||||
|
width: 100px; |
||||
|
margin-top: 10px; |
||||
|
} |
||||
|
|
||||
|
.seed-toggle { |
||||
|
right: 20px; |
||||
|
margin-top: -30px; |
||||
|
top: inherit; |
||||
|
} |
||||
|
|
||||
|
.elections-map { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.elections-map-node { |
||||
|
position: absolute; |
||||
|
|
||||
|
.notary-elections-node-title { |
||||
|
color: #fff !important; |
||||
|
font-weight: bold; |
||||
|
position: absolute; |
||||
|
margin-left: 40px; |
||||
|
margin-top: -6px; |
||||
|
} |
||||
|
|
||||
|
img { |
||||
|
width: 35px; |
||||
|
background: #fff; |
||||
|
border-radius: 50%; |
||||
|
cursor: pointer; |
||||
|
border: none; |
||||
|
box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.2); |
||||
|
transition: width 0.2s ease-in-out; |
||||
|
} |
||||
|
|
||||
|
&.active { |
||||
|
img { |
||||
|
cursor: default; |
||||
|
width: 38px !important; |
||||
|
border: solid 5px #fff; |
||||
|
} |
||||
|
} |
||||
|
&:hover { |
||||
|
img { |
||||
|
width: 40px; |
||||
|
} |
||||
|
} |
||||
|
&.elections-map-node--na { |
||||
|
left: 210px; |
||||
|
top: 258px; |
||||
|
} |
||||
|
&.elections-map-node--sh { |
||||
|
left: 420px; |
||||
|
top: 344px; |
||||
|
} |
||||
|
&.elections-map-node--ae { |
||||
|
left: 560px; |
||||
|
top: 284px; |
||||
|
} |
||||
|
&.elections-map-node--eu { |
||||
|
left: 440px; |
||||
|
top: 250px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.elections-balance, |
||||
|
.elections-history { |
||||
|
font-size: 16px; |
||||
|
} |
||||
|
|
||||
|
.elections-history { |
||||
|
margin-bottom: 10px; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.elections-send { |
||||
|
.form-control { |
||||
|
display: inline-block; |
||||
|
width: 250px; |
||||
|
} |
||||
|
.btn { |
||||
|
position: relative; |
||||
|
top: -6px; |
||||
|
left: 15px; |
||||
|
display: inline-block; |
||||
|
} |
||||
|
.block { |
||||
|
width: 365px; |
||||
|
display: block; |
||||
|
} |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue