Skip to content
This repository has been archived by the owner on Apr 15, 2019. It is now read-only.

Migrate browsing delegates on Voting tab to React - Closes #350 #512

Merged
merged 21 commits into from
Jul 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
51a6c7e
change position of Link components in app component
yasharAyari Jul 24, 2017
7dee886
Create votingComponent component
yasharAyari Jul 24, 2017
a5379e7
Use votingComponent in voting
yasharAyari Jul 24, 2017
0644866
Add new variable to reactToolboxVariables in webpack.config.js
yasharAyari Jul 24, 2017
8785fbe
Create votingHeader component
yasharAyari Jul 24, 2017
78f5aa9
Merge this branch with devepoment branch and resolve conflicts
yasharAyari Jul 24, 2017
383b1f6
Create a test file for voting component
yasharAyari Jul 24, 2017
36311e9
Create a test file for votingComponent component
yasharAyari Jul 24, 2017
bb0a7b1
Create a test file for votingHeader component
yasharAyari Jul 24, 2017
799087d
Create a test file for voting component
yasharAyari Jul 24, 2017
6d451f7
Clean some extra codes in voting component
yasharAyari Jul 24, 2017
f5d442d
Fix some eslint warnings in votingComponent component
yasharAyari Jul 24, 2017
4c71469
Fix some bugs in votingHeader component
yasharAyari Jul 24, 2017
34250a4
Improve test coverage percentage of votingHeader component
yasharAyari Jul 25, 2017
ec89fb9
Merge this branch with devepoment branch and resolve conflicts
yasharAyari Jul 27, 2017
594647b
Add infinite scroll loading to votingComponent component and fix some…
yasharAyari Jul 27, 2017
7336448
Add some global styles to app.css
yasharAyari Jul 27, 2017
95a64b0
Fix some bugs in votingComponent component
yasharAyari Jul 27, 2017
2218b5b
Fix some bugs in votingHeader component
yasharAyari Jul 27, 2017
dda1752
Remove numeric property from TableCell components
yasharAyari Jul 27, 2017
552f33d
Add No delegates found message to votingComponent
yasharAyari Jul 27, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/components/app/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ body{
background-color: #fff;
padding: 16px;
box-sizing: border-box;
&.noPaddingBox{
padding: 16px 0;
}
}
:global .hasPaddingRow{
padding: 0 16px;
}
12 changes: 7 additions & 5 deletions src/components/voting/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React from 'react';
import { connect } from 'react-redux';
import VotingComponent from './votingComponent';

const Voting = () => (
<h1>Voting</h1>
);
const mapStateToProps = state => ({
address: state.account.address,
activePeer: state.peers.data,
});

export default Voting;
export default connect(mapStateToProps)(VotingComponent);
18 changes: 18 additions & 0 deletions src/components/voting/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
import Voting from './';
import store from '../../store';

describe('Voting', () => {
let wrapper;

beforeEach(() => {
wrapper = mount(<Provider store={store}><Voting /></Provider>);
});

it('should render VotingComponent', () => {
expect(wrapper.find('VotingComponent')).to.have.lengthOf(1);
});
});
34 changes: 34 additions & 0 deletions src/components/voting/voting.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.table{
& :global thead th:first-child{
display: none !important;
}
}
.searchBox{
position: relative;
}
.searchIcon{
position: absolute;
top: 27px;
right: 10px;
color: rgba(0, 0, 0, .38);
}
.field{
margin-bottom: 0 !important;
}
.row{
&:hover{
background: #fff;
}
}
.upVoteRow {
background-color: rgb(226, 238, 213) !important;
}
.downVoteRow {
background-color: rgb(255, 228, 220) !important;
}
.votedRow{
background-color: #d6f0ff !important;
}
.pendingRow {
background-color: #eaeae9 !important;
}
156 changes: 156 additions & 0 deletions src/components/voting/votingComponent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import React from 'react';
import { Table, TableHead, TableRow, TableCell } from 'react-toolbox/lib/table';
import Waypoint from 'react-waypoint';
import Checkbox from 'react-toolbox/lib/checkbox';
import { listAccountDelegates, listDelegates } from '../../utils/api/delegate';
import VotingHeader from './votingHeader';
import styles from './voting.css';

const setRowClass = (item) => {
let className = '';
if (item.status.selected && item.status.voted) {
className = styles.votedRow;
} else if (!item.status.selected && item.status.voted) {
className = styles.downVoteRow;
} else if (item.status.selected && !item.status.voted) {
className = styles.upVoteRow;
}
return className;
};

class VotingComponent extends React.Component {
constructor() {
super();
this.state = {
delegates: [],
selected: [],
query: '',
offset: 0,
loadMore: true,
length: 1,
notFound: '',
};
this.delegates = [];
this.query = '';
}
componentDidMount() {
listAccountDelegates(this.props.activePeer, this.props.address).then((res) => {
this.votedDelegates = [];
res.delegates.forEach(delegate => this.votedDelegates.push(delegate.username));
this.loadDelegates(this.query);
});
}
/**
* Fetches a list of delegates based on the given search phrase
* @param {string} query - username of a delegate
*/
search(query) {
this.query = query;
this.setState({
offset: 0,
delegates: [],
length: 1,
loadMore: false,
});
setTimeout(() => {
this.loadDelegates(this.query);
}, 100);
}
/**
* Fetches a list of delegates
*
* @method loadDelegates
* @param {String} search - The search phrase to match with the delegate name
* should replace the old delegates list
* @param {Number} limit - The maximum number of results
*/
loadDelegates(search, limit = 100) {
this.setState({ loadMore: false });
listDelegates(
this.props.activePeer,
{
offset: this.state.offset,
limit: limit.toString(),
q: search,
},
).then((res) => {
const delegatesList = res.delegates
.map(delegate => this.setStatus(delegate));
this.setState({
delegates: [...this.state.delegates, ...delegatesList],
offset: this.state.offset + delegatesList.length,
length: parseInt(res.totalCount, 10),
loadMore: true,
notFound: delegatesList.length > 0 ? '' : <div className="hasPaddingRow">No delegates found</div>,
});
});
}
/**
* Sets deleagte.status to be always the same object for given delegate.address
*/
setStatus(delegate) {
let item = delegate;// eslint-disable-line
const voted = this.votedDelegates.indexOf(delegate.username) > -1;
item.status = {
voted,
selected: voted,
};
return item;
}

/**
* change status of selected row
* @param {integer} index - index of row that we want to change status of that
* @param {boolian} value - value of checkbox
*/
handleChange(index, value) {
let delegates = this.state.delegates; // eslint-disable-line
delegates[index].status.selected = value;
this.setState({ delegates });
}
/**
* load more data when scroll bar reachs end of the page
*/
loadMore() {
if (this.state.loadMore && this.state.length > this.state.offset) {
this.loadDelegates(this.query);
}
}
render() {
return (
<div className="box noPaddingBox">
<VotingHeader search={ value => this.search(value) }></VotingHeader>
<Table selectable={false}
>
<TableHead displaySelect={false}>
<TableCell>Vote</TableCell>
<TableCell>Rank</TableCell>
<TableCell>Name</TableCell>
<TableCell>Lisk Address</TableCell>
<TableCell>Uptime</TableCell>
<TableCell>Approval</TableCell>
</TableHead>
{this.state.delegates.map((item, idx) => (
<TableRow key={idx} className={`${styles.row} ${setRowClass(item)}`}>
<TableCell>
<Checkbox className={styles.field}
checked={item.status.selected}
onChange={this.handleChange.bind(this, idx)}
/>
</TableCell>
<TableCell>{item.rank}</TableCell>
<TableCell>{item.username}</TableCell>
<TableCell>{item.address}</TableCell>
<TableCell>{item.productivity} %</TableCell>
<TableCell>{item.approval} %</TableCell>
</TableRow>
))}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a No delegates found message here if the search result is empty.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is fixed

</Table>
{this.state.notFound}
<Waypoint onEnter={this.loadMore.bind(this)}></Waypoint>
</div>
);
}
}

export default VotingComponent;
22 changes: 22 additions & 0 deletions src/components/voting/votingComponent.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import VotingComponent from './votingComponent';

describe('VotingComponent', () => {
const address = '16313739661670634666L';
const activePeer = {};
let wrapper;

beforeEach(() => {
wrapper = mount(<VotingComponent activePeer={activePeer} address={address}></VotingComponent>);
});

it('should render VotingHeader', () => {
expect(wrapper.find('VotingHeader')).to.have.lengthOf(1);
});

it('should render Table', () => {
expect(wrapper.find('Table')).to.have.lengthOf(1);
});
});
43 changes: 43 additions & 0 deletions src/components/voting/votingHeader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import grid from 'flexboxgrid/dist/flexboxgrid.css';
import Input from 'react-toolbox/lib/input';
import styles from './voting.css';

class VotingHeader extends React.Component {
constructor() {
super();
this.state = {
query: '',
searchIcon: 'search',
};
}
search(name, value) {
const icon = value.length > 0 ? 'close' : 'search';
this.setState({
query: value,
searchIcon: icon,
});
this.props.search(value);
}
clearSearch() {
if (this.state.searchIcon === 'close') {
this.search('query', '');
}
}
render() {
return (
<header className={`${grid.row} hasPaddingRow`}>
<div className={`${grid['col-xs-3']} ${styles.searchBox}`}>
<Input type='tel' label='Search' name='query'
value={this.state.query}
onChange={this.search.bind(this, 'query')}
/>
<i className={`material-icons ${styles.searchIcon}`} onClick={ this.clearSearch.bind(this) }>
{this.state.searchIcon}
</i>
</div>
</header>
);
}
}
export default VotingHeader;
47 changes: 47 additions & 0 deletions src/components/voting/votingHeader.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import chai, { expect } from 'chai';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { mount } from 'enzyme';
import VotingHeader from './votingHeader';

chai.use(sinonChai);
describe('VotingHeader', () => {
const search = sinon.spy();
let wrapper;
beforeEach(() => {
wrapper = mount(<VotingHeader search={search}></VotingHeader>);
});

it('should render an Input', () => {
expect(wrapper.find('Input')).to.have.lengthOf(1);
});

it('should render i.material-icons with text of "search" when this.search is not called', () => {
// expect(wrapper.find('i.material-icons')).to.have.lengthOf(1);
expect(wrapper.find('i.material-icons').text()).to.be.equal('search');
});

it('should render i.material-icons with text of "close" when this.search is called', () => {
wrapper.instance().search('query', '555');
expect(wrapper.find('i.material-icons').text()).to.be.equal('close');
});

it('should this.props.search when this.search is called', () => {
wrapper.instance().search('query', '555');
expect(search).to.have.been.calledWith('555');
});


it('should this.props.search when this.search is called', () => {
wrapper.instance().search('query', '555');
expect(search).to.have.been.calledWith('555');
});


it('click on i.material-icons should clear vlaue of search input', () => {
wrapper.instance().search('query', '555');
wrapper.find('i.material-icons').simulate('click')
expect(wrapper.state('query')).to.be.equal('');
});
});
1 change: 1 addition & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const reactToolboxVariables = {
'color-primary': '#0288D1',
'color-primary-dark': '#0288D1',
'button-border-radius': '3px',
'input-text-label-color': 'rgba(0,0,0,0.38)',
};

let entries = {
Expand Down