Skip to content

Commit

Permalink
Merge pull request #2048 from LiskHQ/1972-implement-voting-summary-page
Browse files Browse the repository at this point in the history
Implement voting summary page - Closes #1972
  • Loading branch information
slaweet committed May 23, 2019
2 parents f2f4f57 + 31f08b3 commit b582790
Show file tree
Hide file tree
Showing 28 changed files with 1,091 additions and 251 deletions.
11 changes: 11 additions & 0 deletions i18n/locales/en/common.json
Expand Up @@ -72,6 +72,7 @@
"Cancel": "Cancel",
"Cancel voting": "Cancel voting",
"Check for updates...": "Check for updates...",
"Check spelling – delegate name does not exist": "Check spelling – delegate name does not exist",
"Check spelling – name does not exist on mainnet": "Check spelling – name does not exist on mainnet",
"Choose": "Choose",
"Choose a name": "Choose a name",
Expand All @@ -88,8 +89,10 @@
"Confirm (Fee: {{fee}} LSK)": "Confirm (Fee: {{fee}} LSK)",
"Confirm on {{deviceModel}}": "Confirm on {{deviceModel}}",
"Confirm to register your second passphrase on the blockchain.": "Confirm to register your second passphrase on the blockchain.",
"Confirm transaction on your {{deviceModel}}": "Confirm transaction on your {{deviceModel}}",
"Confirm transaction on {{deviceModel}}": "Confirm transaction on {{deviceModel}}",
"Confirm vote on {{deviceModel}}": "Confirm vote on {{deviceModel}}",
"Confirm voting": "Confirm voting",
"Confirm your name": "Confirm your name",
"Confirm your passphrase": "Confirm your passphrase",
"Confirmation in the next step": "Confirmation in the next step",
Expand Down Expand Up @@ -153,6 +156,7 @@
"Each time you add or remove a vote it is counted as an action. There's {{fee}} LSK fee per every 33 actions.": "Each time you add or remove a vote it is counted as an action. There's {{fee}} LSK fee per every 33 actions.",
"Edit": "Edit",
"Edit transaction": "Edit transaction",
"Edit voting": "Edit voting",
"Enter URL of the *.js file with the extension": "Enter URL of the *.js file with the extension",
"Enter your 1st passphrase to confirm": "Enter your 1st passphrase to confirm",
"Enter your passphrase": "Enter your passphrase",
Expand All @@ -165,6 +169,7 @@
"Error retrieving convertion rates.": "Error retrieving convertion rates.",
"Error retrieving dynamic fees.": "Error retrieving dynamic fees.",
"Every transaction needs to be confirmed and forged into Lisks blockchain network. \n Such operations require hardware resources and because of that there is a small fee for processing those.": "Every transaction needs to be confirmed and forged into Lisks blockchain network. \n Such operations require hardware resources and because of that there is a small fee for processing those.",
"Every transaction needs to be confirmed and forged into Lisks blockchain network. \n Such operations require hardware resources and because of that there is a small fee for processing those.": "Every transaction needs to be confirmed and forged into Lisks blockchain network. \n Such operations require hardware resources and because of that there is a small fee for processing those.",
"Expand your knowledge": "Expand your knowledge",
"Explain Blockchain Like I'm 5": "Explain Blockchain Like I'm 5",
"Explore as a Guest": "Explore as a Guest",
Expand Down Expand Up @@ -215,6 +220,7 @@
"ID already added to bookmarks": "ID already added to bookmarks",
"If you’re not sure how to do this please follow the": "If you’re not sure how to do this please follow the",
"In": "In",
"In order to use this Lisk Hub feature you need to sign in to your Lisk account.": "In order to use this Lisk Hub feature you need to sign in to your Lisk account.",
"In this section of Lisk Hub you can vote for up to 101 delegates to run Lisk’s blockchain network and by doing so have a real impact on the Lisk ecosystem.": "In this section of Lisk Hub you can vote for up to 101 delegates to run Lisk’s blockchain network and by doing so have a real impact on the Lisk ecosystem.",
"Include your operating system and screen resolution in your report": "Include your operating system and screen resolution in your report",
"Incoming": "Incoming",
Expand Down Expand Up @@ -337,12 +343,14 @@
"Paste": "Paste",
"Pending...": "Pending...",
"Perfect! You’re all set.": "Perfect! You’re all set.",
"Please check if all the transaction details are correct.": "Please check if all the transaction details are correct.",
"Please check the address": "Please check the address",
"Please check the highlighted words": "Please check the highlighted words",
"Please click Next, then move around your mouse randomly to generate a random passphrase.": "Please click Next, then move around your mouse randomly to generate a random passphrase.",
"Please go back and check your passphrase again.": "Please go back and check your passphrase again.",
"Please keep it safe!": "Please keep it safe!",
"Please select the account you’d like to sign in to or": "Please select the account you’d like to sign in to or",
"Please sign in": "Please sign in",
"Please sign in with your passphrase": "Please sign in with your passphrase",
"Please sign in with your second passphrase": "Please sign in with your second passphrase",
"Please use only digits and dots": "Please use only digits and dots",
Expand All @@ -352,6 +360,7 @@
"Print more than one copy and store them in two separate secure places.": "Print more than one copy and store them in two separate secure places.",
"Privacy Policy": "Privacy Policy",
"Processing Speed": "Processing Speed",
"Processing...": "Processing...",
"Productivity": "Productivity",
"Provide a correct amount of {{token}}": "Provide a correct amount of {{token}}",
"Provide a correct wallet address or a name of a bookmarked account": "Provide a correct wallet address or a name of a bookmarked account",
Expand Down Expand Up @@ -525,8 +534,10 @@
"Voted delegates": "Voted delegates",
"Voter": "Voter",
"Votes": "Votes",
"Votes after confirmation": "Votes after confirmation",
"Votes submitted": "Votes submitted",
"Voting": "Voting",
"Voting summary": "Voting summary",
"Wallet": "Wallet",
"Want to dig deeper? We got you covered. You can read more about Lisk’s delgates, voting mechanism and benefits in a dedicated section of Lisk’s help centre.": "Want to dig deeper? We got you covered. You can read more about Lisk’s delgates, voting mechanism and benefits in a dedicated section of Lisk’s help centre.",
"We recommend including date & time or a specific keyword.": "We recommend including date & time or a specific keyword.",
Expand Down
2 changes: 0 additions & 2 deletions jest.config.js
Expand Up @@ -20,7 +20,6 @@ module.exports = {
'src/store/middlewares/socket.test.js',
'src/store/middlewares/peers.test.js',
'src/components/votingListViewV2/*',
'src/components/votingV2/*',
'src/components/registerV2/registerV2.test.js',
'src/components/headerV2/headerV2.test.js',
],
Expand Down Expand Up @@ -132,7 +131,6 @@ module.exports = {
'src/components/loginV2/loginV2.js',
'src/utils/hwWallet.js',
'src/components/votingListViewV2/*',
'src/components/votingV2/*',
'src/components/headerV2/headerV2.js',
'src/components/registerV2/registerV2.js',
],
Expand Down
74 changes: 74 additions & 0 deletions src/components/delegatesV2/delegatesV2.js
@@ -0,0 +1,74 @@
import React from 'react';
import grid from 'flexboxgrid/dist/flexboxgrid.css';
import styles from './votingV2.css';
import VotingListViewV2 from '../votingListViewV2';
import VotingHeader from './votingHeader';
import Onboarding from '../toolbox/onboarding/onboarding';
import { getTotalActions } from './../../utils/voting';

class DelegatesV2 extends React.Component {
constructor({ votes, ...props }) {
super(props);

this.state = {
votingModeEnabled: getTotalActions(votes) > 0,
};

this.toggleVotingMode = this.toggleVotingMode.bind(this);
this.getOnboardingSlides = this.getOnboardingSlides.bind(this);
}

toggleVotingMode() {
if (this.state.votingModeEnabled) {
this.props.clearVotes();
}
this.setState({ votingModeEnabled: !this.state.votingModeEnabled });
}

getOnboardingSlides() {
const { t } = this.props;
return [{
title: t('Welcome to Lisk Delegates!'),
content: t('Lisk blockchain network is based on a Delegated Proof of Stake consensus algorithm, in which 101 delegates are chosen to run the network by the community.'),
illustration: 'welcomeLiskDelegates',
}, {
title: t('Your voice matters'),
content: t('In this section of Lisk Hub you can vote for up to 101 delegates to run Lisk’s blockchain network and by doing so have a real impact on the Lisk ecosystem.'),
illustration: 'yourVoiceMatters',
}, {
title: t('Get rewarded by the community'),
content: t('Some delegates offer to share a certain percentage of their earnings from running the network with the users who vote for them. You can find more information on Lisk’s Reddit or Rocketchat.'),
illustration: 'getRewarded',
}, {
title: t('Expand your knowledge'),
content: t('Want to dig deeper? We got you covered. You can read more about Lisk’s delgates, voting mechanism and benefits in a dedicated section of Lisk’s help centre.'),
illustration: 'expandYourKnowledge',
}];
}
render() {
const { t, votes } = this.props;
const { votingModeEnabled } = this.state;
return (
<div className={`${grid.row} ${styles.wrapper}`} ref={(el) => { this.root = el; }}>
<Onboarding
slides={this.getOnboardingSlides()}
finalCallback={this.toggleVotingMode}
ctaLabel={t('Start voting')}
name={'delegateOnboarding'}
/>
<VotingHeader
t={t}
votingModeEnabled={votingModeEnabled}
toggleVotingMode={this.toggleVotingMode}
votes={votes}/>
<section className={`${grid['col-sm-12']} ${grid['col-md-12']} ${styles.votingBox} ${styles.votes}`}>
<VotingListViewV2
votingModeEnabled={votingModeEnabled}
/>
</section>
</div>
);
}
}

export default DelegatesV2;
69 changes: 69 additions & 0 deletions src/components/delegatesV2/delegatesV2.test.js
@@ -0,0 +1,69 @@
import React from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import PropTypes from 'prop-types';
import thunk from 'redux-thunk';
import { BrowserRouter as Router } from 'react-router-dom';
import { prepareStore } from '../../../test/unit-test-utils/applicationInit';
import peersReducer from '../../store/reducers/peers';
import accountReducer from '../../store/reducers/account';
import votingReducer from '../../store/reducers/voting';
import DelegatesV2 from './delegatesV2';
import history from '../../history';
import i18n from '../../i18n';

describe('DelegatesV2', () => {
let wrapper;
const votes = {
username1: { confirmed: true, unconfirmed: true, publicKey: 'sample_key' },
};
const store = prepareStore({
peers: peersReducer,
account: accountReducer,
voting: votingReducer,
}, [thunk]);

const delegates = [
{
address: 'address 1',
username: 'username1',
publicKey: 'sample_key',
serverPublicKey: 'sample_key',
rank: 12,
},
{
address: 'address 2',
username: 'username2',
publicKey: 'sample_key',
serverPublicKey: 'sample_key',
rank: 23,
},
];
const props = {
refreshDelegates: false,
delegates,
totalDelegates: 10,
votes,
t: key => key,
history: { location: { search: '' } },
clearVotes: jest.fn(),
};
const options = {
context: { store, history, i18n },
childContextTypes: {
store: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
i18n: PropTypes.object.isRequired,
},
};

it('should allow to enable and disable voting mode', () => {
wrapper = mount(<Router><DelegatesV2 {...props} /></Router>, options);
wrapper.find('.start-voting-button').at(0).simulate('click');
expect(wrapper.find('.addedVotes')).to.have.lengthOf(1);

wrapper.find('.cancel-voting-button').at(0).simulate('click');
expect(wrapper.find('.addedVotes')).to.have.lengthOf(0);
});
});

15 changes: 15 additions & 0 deletions src/components/delegatesV2/index.js
@@ -0,0 +1,15 @@
/* istanbul ignore file */
import { connect } from 'react-redux';
import { translate } from 'react-i18next';
import DelegatesV2 from './delegatesV2';
import { clearVotes } from '../../actions/voting';

const mapStateToProps = state => ({
votes: state.voting.votes,
});

const mapDispatchToProps = {
clearVotes,
};

export default connect(mapStateToProps, mapDispatchToProps)(translate()(DelegatesV2));
Expand Up @@ -6,6 +6,10 @@
justify-content: space-between;
width: 100%;
margin-bottom: 25px;

& > * {
display: flex;
}
}

.box,
Expand Down
Expand Up @@ -2,12 +2,14 @@ import React from 'react';
import { Link } from 'react-router-dom';
import { SecondaryButtonV2, PrimaryButtonV2 } from '../toolbox/buttons/button';
import Tooltip from '../toolbox/tooltip/tooltip';
import SignInTooltipWrapper from '../signInTooltipWrapper';
import routes from './../../constants/routes';
import votingConst from '../../constants/voting';
import {
getTotalVotesCount,
getVoteList,
getUnvoteList,
getTotalActions,
} from './../../utils/voting';

import styles from './votingHeader.css';
Expand All @@ -22,14 +24,11 @@ class VotingHeader extends React.Component {
} = this.props;
const voteList = getVoteList(votes);
const unvoteList = getUnvoteList(votes);
const totalActions = getTotalActions(votes);
const {
maxCountOfVotesInOneTurn,
maxCountOfVotes,
fee,
} = votingConst;
const totalActions = Math.ceil((
voteList.length + unvoteList.length
) / maxCountOfVotesInOneTurn);
return (
<div className={`${styles.wrapper}`}>
<span>
Expand Down Expand Up @@ -64,22 +63,28 @@ class VotingHeader extends React.Component {
</span>
{ votingModeEnabled ?
<span>
<SecondaryButtonV2 onClick={toggleVotingMode} className={styles.btn}>
<SecondaryButtonV2 onClick={toggleVotingMode} className={`cancel-voting-button ${styles.btn}`}>
{t('Cancel voting')}
</SecondaryButtonV2>
<PrimaryButtonV2 className={styles.btn} disabled={totalActions === 0}>
{t('Go to Confirmation')}
</PrimaryButtonV2>
<Link to={routes.voting.path} >
<PrimaryButtonV2 className={styles.btn} disabled={totalActions === 0}>
{t('Go to Confirmation')}
</PrimaryButtonV2>
</Link>
</span> :
<span>
<Link to={routes.registerDelegate.path} >
<SecondaryButtonV2 className={`register-delegate ${styles.btn}`}>
{t('Register as a Delegate')}
</SecondaryButtonV2>
</Link>
<PrimaryButtonV2 onClick={toggleVotingMode} className={styles.btn}>
{t('Start voting')}
</PrimaryButtonV2>
<SignInTooltipWrapper>
<Link to={routes.registerDelegate.path} >
<SecondaryButtonV2 className={`register-delegate ${styles.btn}`}>
{t('Register as a Delegate')}
</SecondaryButtonV2>
</Link>
</SignInTooltipWrapper>
<SignInTooltipWrapper>
<PrimaryButtonV2 onClick={toggleVotingMode} className={`start-voting-button ${styles.btn}`}>
{t('Start voting')}
</PrimaryButtonV2>
</SignInTooltipWrapper>
</span>
}
</div>
Expand Down

0 comments on commit b582790

Please sign in to comment.