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

Commit

Permalink
Merge pull request #495 from LiskHQ/351-forging-tab
Browse files Browse the repository at this point in the history
Migrate forging component to React - Closes #351
  • Loading branch information
slaweet committed Jul 24, 2017
2 parents cb0aff7 + 84ec037 commit 42de0c7
Show file tree
Hide file tree
Showing 29 changed files with 730 additions and 28 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -36,6 +36,7 @@
"postcss-cssnext": "=2.11.0",
"prop-types": "=15.5.10",
"react": "=15.6.x",
"react-circular-progressbar": "=0.1.5",
"react-dom": "=15.6.x",
"react-redux": "=5.0.3",
"react-redux-toastr": "=7.0.0",
Expand Down
11 changes: 11 additions & 0 deletions src/actions/forging.js
@@ -0,0 +1,11 @@
import actionTypes from '../constants/actions';

export const updateForgedBlocks = data => ({
data,
type: actionTypes.forgedBlocksUpdated,
});

export const updateForgingStats = data => ({
data,
type: actionTypes.forgingStatsUpdated,
});
27 changes: 27 additions & 0 deletions src/actions/forging.test.js
@@ -0,0 +1,27 @@
import { expect } from 'chai';
import actionTypes from '../constants/actions';
import { updateForgedBlocks, updateForgingStats } from './forging';

describe('actions', () => {
it('should create an action to update forged blocks', () => {
const data = {
online: true,
};

const expectedAction = {
data,
type: actionTypes.forgedBlocksUpdated,
};
expect(updateForgedBlocks(data)).to.be.deep.equal(expectedAction);
});

it('should create an action to update forging stats', () => {
const data = { last7d: 1000 };

const expectedAction = {
data,
type: actionTypes.forgingStatsUpdated,
};
expect(updateForgingStats(data)).to.be.deep.equal(expectedAction);
});
});
2 changes: 1 addition & 1 deletion src/components/account/address.js
Expand Up @@ -6,7 +6,7 @@ const Address = (props) => {
const content = props.isDelegate ?
(<div>
<p className="inner primary">
{props.username}
{props.delegate.username}
</p>
<p className="inner secondary">
<span>{props.address}</span>
Expand Down
8 changes: 6 additions & 2 deletions src/components/account/address.test.js
Expand Up @@ -18,7 +18,9 @@ describe('Address', () => {
const inputValue = {
isDelegate: true,
address: '16313739661670634666L',
username: 'lisk-nano',
delegate: {
username: 'lisk-nano',
},
};
const expectedHeaderValue = 'Delegate';
const wrapper = shallow(<Address {...inputValue} />);
Expand All @@ -29,7 +31,9 @@ describe('Address', () => {
const inputValue = {
isDelegate: true,
address: '16313739661670634666L',
username: 'lisk-nano',
delegate: {
username: 'lisk-nano',
},
};
const expectedValue = 'lisk-nano';
const wrapper = shallow(<Address {...inputValue} />);
Expand Down
7 changes: 3 additions & 4 deletions src/components/app/index.js
Expand Up @@ -23,6 +23,9 @@ const App = () => {
<Route path="/main" render={({ match }) => (
<main className=''>
<Account />
<Link to='/main/transactions'>Transactions</Link>
<Link to='/main/voting'>Voting</Link>
<Link to='/main/forging'>Forging</Link>
<Route path={`${match.url}/transactions`} component={Transactions}/>
<Route path={`${match.url}/voting`} component={Voting}/>
<Route path={`${match.url}/forging`} component={Forging}/>
Expand All @@ -31,10 +34,6 @@ const App = () => {
<Route exact path="/" component={Login} />
</main>

<Link to='/'>Login</Link>
<Link to='/main/transactions'>Transactions</Link>
<Link to='/main/voting'>Voting</Link>
<Link to='/main/forging'>Forging</Link>
<Dialog />
</section>
);
Expand Down
24 changes: 24 additions & 0 deletions src/components/forging/circularProgressbar.css
@@ -0,0 +1,24 @@
:global .CircularProgressbar {
/*
* This fixes an issue where the CircularProgressbar svg has
* 0 width inside a "display: flex" container, and thus not visible.
*
* If you're not using "display: flex", you can remove this style.
*/
width: 100%;
}

:global .CircularProgressbar .CircularProgressbar-path {
stroke: rgb(2, 136, 209);
transition: stroke-dashoffset 0.5s ease 0s;
}

:global .CircularProgressbar .CircularProgressbar-trail {
stroke: #d6d6d6;
}

:global .CircularProgressbar .CircularProgressbar-text {
font-size: 20px;
dominant-baseline: middle;
text-anchor: middle;
}
50 changes: 50 additions & 0 deletions src/components/forging/delegateStats.js
@@ -0,0 +1,50 @@
import React from 'react';
import { Card, CardText } from 'react-toolbox/lib/card';
import CircularProgressbar from 'react-circular-progressbar';
import grid from '../../../node_modules/flexboxgrid/dist/flexboxgrid.css';
import style from './forging.css';

const identity = x => (x);
const addPercentSign = x => (`${x}%`);

const progressCircleCardList = [
{
key: 'rate',
label: 'Rank',
percentageTransform: percentage => (Math.max(0, 101 - percentage)),
textForPercentage: identity,
}, {
key: 'productivity',
label: 'Productivity',
percentageTransform: identity,
textForPercentage: addPercentSign,
}, {
key: 'approval',
label: 'Approval',
percentageTransform: identity,
textForPercentage: addPercentSign,
},
];

const DelegateStats = props => (
<div className={`${grid.row} ${grid['between-xs']}`}>
{progressCircleCardList.map(cardItem => (
<div className={grid['col-xs-4']} key={cardItem.key}>
<Card className={style.grayCard}>
<CardText>
<div className={grid['col-xs-12']}>
<div className={`${grid.row} ${grid['between-xs']}`}>
<div className={style.circularProgressTitle}> {cardItem.label} </div>
<CircularProgressbar
percentage={cardItem.percentageTransform(props.delegate[cardItem.key])}
textForPercentage={cardItem.textForPercentage.bind(props.delegate[cardItem.key])}/>
</div>
</div>
</CardText>
</Card>
</div>
))}
</div>
);

export default DelegateStats;
29 changes: 29 additions & 0 deletions src/components/forging/delegateStats.test.js
@@ -0,0 +1,29 @@
import React from 'react';
import chai, { expect } from 'chai';
import sinonChai from 'sinon-chai';
import { mount } from 'enzyme';
import DelegateStats from './delegateStats';

chai.use(sinonChai);

describe('DelegateStats', () => {
const delegate = {
username: 'genesis_17',
rate: 19,
approval: 30,
productivity: 99.2,
};
let wrapper;

beforeEach(() => {
wrapper = mount(<DelegateStats delegate={delegate} />);
});

it('should render 3 Card components', () => {
expect(wrapper.find('Card')).to.have.lengthOf(3);
});

it('should render 3 CircularProgressbar components', () => {
expect(wrapper.find('svg.CircularProgressbar')).to.have.lengthOf(3);
});
});
39 changes: 39 additions & 0 deletions src/components/forging/forgedBlocks.js
@@ -0,0 +1,39 @@
import React from 'react';
import { Card, CardTitle } from 'react-toolbox/lib/card';
import { Table, TableHead, TableRow, TableCell } from 'react-toolbox/lib/table';
import { TooltipTime } from '../timestamp';
import LiskAmount from '../liskAmount';
import FormattedNumber from '../formattedNumber';
import grid from '../../../node_modules/flexboxgrid/dist/flexboxgrid.css';
import style from './forging.css';


const ForgedBlocks = props => (
<Card className={`${style.grayCard} ${grid['col-xs-12']}`}>
<CardTitle>
Forged Blocks
</CardTitle>
<div className={style.forgedBlocksTableWrapper}>
<Table selectable={false}>
<TableHead>
<TableCell>Block height</TableCell>
<TableCell>Block Id</TableCell>
<TableCell>Timestamp</TableCell>
<TableCell>Total fee</TableCell>
<TableCell>Reward</TableCell>
</TableHead>
{props.forgedBlocks.map((block, idx) => (
<TableRow key={idx}>
<TableCell><FormattedNumber val={block.height} /></TableCell>
<TableCell>{block.id}</TableCell>
<TableCell><TooltipTime label={block.timestamp} /></TableCell>
<TableCell><LiskAmount val={block.totalFee} /></TableCell>
<TableCell><LiskAmount val={block.reward} /></TableCell>
</TableRow>
))}
</Table>
</div>
</Card>
);

export default ForgedBlocks;
52 changes: 52 additions & 0 deletions src/components/forging/forgedBlocks.test.js
@@ -0,0 +1,52 @@
import React from 'react';
import chai, { expect } from 'chai';
import sinonChai from 'sinon-chai';
import { mount } from 'enzyme';
import ForgedBlocks from './forgedBlocks';

chai.use(sinonChai);

describe('ForgedBlocks', () => {
const forgedBlocks = [{
id: '16113150790072764126',
timestamp: 36280810,
height: 29394,
totalFee: 0,
reward: 0,
},
{
id: '13838471839278892195',
version: 0,
timestamp: 36280700,
height: 29383,
totalFee: 0,
reward: 0,
},
{
id: '5654150596698663763',
version: 0,
timestamp: 36279700,
height: 29283,
totalFee: 0,
reward: 0,
},
];
let wrapper;

beforeEach(() => {
wrapper = mount(<ForgedBlocks forgedBlocks={forgedBlocks} />);
});

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

it('should render TableHead component with 5 TableCell componenets', () => {
expect(wrapper.find('TableHead')).to.have.lengthOf(1);
expect(wrapper.find('TableHead').find('TableCell')).to.have.lengthOf(5);
});

it('should render 3 TableRow components', () => {
expect(wrapper.find('TableRow')).to.have.lengthOf(3);
});
});
20 changes: 20 additions & 0 deletions src/components/forging/forging.css
@@ -0,0 +1,20 @@
@import './circularProgressbar.css';

.delegateName {
margin: 0;
font-weight: normal;
}

.grayCard {
background: #f7f8f9;
}

.forgedBlocksTableWrapper {
margin: 0 -8px;
}

.circularProgressTitle {
text-align: center;
padding-bottom: 16px;
width: 100%;
}
60 changes: 60 additions & 0 deletions src/components/forging/forgingComponent.js
@@ -0,0 +1,60 @@
import React from 'react';
import { Card } from 'react-toolbox/lib/card';
import Waypoint from 'react-waypoint';
import { getForgedBlocks, getForgedStats } from '../../utils/api/forging';
import ForgingTitle from './forgingTitle';
import DelegateStats from './delegateStats';
import ForgingStats from './forgingStats';
import ForgedBlocks from './forgedBlocks';

class ForgingComponent extends React.Component {
loadStats(key, startMoment) {
getForgedStats(this.props.peers.data, startMoment, this.props.account.publicKey,
).then((data) => {
this.props.onForgingStatsUpdate({ [key]: data.forged });
});
}

loadForgedBlocks(activePeer, limit, offset, generatorPublicKey) {
getForgedBlocks(activePeer, limit, offset, generatorPublicKey).then((data) => {
this.props.onForgedBlocksLoaded(data.blocks);
});
}

render() {
return (
<Card style={{ padding: 8 }}>
{this.props.account && this.props.account.isDelegate ?
<div>
<ForgingTitle account={this.props.account} statistics={this.props.statistics}
loadStats={this.loadStats.bind(this)} />
<br />
<ForgingStats account={this.props.account} statistics={this.props.statistics}
loadStats={this.loadStats.bind(this)} />
<br />
<DelegateStats delegate={this.props.account.delegate} />
<br />
<ForgedBlocks forgedBlocks={this.props.forgedBlocks} />
<Waypoint onEnter={() => this.loadForgedBlocks(
this.props.peers.data,
20,
this.props.forgedBlocks.length,
this.props.account.publicKey,
) } />
</div> :
null
}
{this.props.account && this.props.account.delegate && !this.props.account.isDelegate ?
<p>
You need to become a delegate to start forging.
If you already registered to become a delegate,
your registration hasn't been processed, yet.
</p> :
null
}
</Card>
);
}
}

export default ForgingComponent;

0 comments on commit 42de0c7

Please sign in to comment.