This repository has been archived by the owner on Apr 15, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 60
Migrate browsing delegates on Voting tab to React - Closes #350 #512
Merged
yasharAyari
merged 21 commits into
development
from
350-migrate-browsing-delegates-to-react
Jul 27, 2017
Merged
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 7dee886
Create votingComponent component
yasharAyari a5379e7
Use votingComponent in voting
yasharAyari 0644866
Add new variable to reactToolboxVariables in webpack.config.js
yasharAyari 8785fbe
Create votingHeader component
yasharAyari 78f5aa9
Merge this branch with devepoment branch and resolve conflicts
yasharAyari 383b1f6
Create a test file for voting component
yasharAyari 36311e9
Create a test file for votingComponent component
yasharAyari bb0a7b1
Create a test file for votingHeader component
yasharAyari 799087d
Create a test file for voting component
yasharAyari 6d451f7
Clean some extra codes in voting component
yasharAyari f5d442d
Fix some eslint warnings in votingComponent component
yasharAyari 4c71469
Fix some bugs in votingHeader component
yasharAyari 34250a4
Improve test coverage percentage of votingHeader component
yasharAyari ec89fb9
Merge this branch with devepoment branch and resolve conflicts
yasharAyari 594647b
Add infinite scroll loading to votingComponent component and fix some…
yasharAyari 7336448
Add some global styles to app.css
yasharAyari 95a64b0
Fix some bugs in votingComponent component
yasharAyari 2218b5b
Fix some bugs in votingHeader component
yasharAyari dda1752
Remove numeric property from TableCell components
yasharAyari 552f33d
Add No delegates found message to votingComponent
yasharAyari File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
))} | ||
</Table> | ||
{this.state.notFound} | ||
<Waypoint onEnter={this.loadMore.bind(this)}></Waypoint> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default VotingComponent; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(''); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is fixed