feat: added kudos wall (#24)

* feat: leaderboard for admins

* feat: added kudos wall
This commit is contained in:
Richard Ramos 2019-05-07 09:37:54 -04:00 committed by GitHub
parent 445e7f8471
commit 7233fb934b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 111 additions and 47 deletions

View File

@ -10,14 +10,17 @@ const Header = ({ isUserAdmin }) => (
<img alt="Logo" src={logo} className="mr-3" />
Status Meritocracy
</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
{isUserAdmin && (
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="mr-auto">
<Nav.Link href="#/">Home</Nav.Link>
<Nav.Link href="#/admin">Admin</Nav.Link>
</Nav>
</Navbar.Collapse>
<React.Fragment>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="mr-auto">
<Nav.Link href="#/">Home</Nav.Link>
<Nav.Link href="#/admin">Admin</Nav.Link>
<Nav.Link href="#/wall">The Wall</Nav.Link>
</Nav>
</Navbar.Collapse>
</React.Fragment>
)}
</Navbar>
);

View File

@ -1,8 +1,8 @@
/*global web3*/
import React, { Fragment } from 'react';
import { Tabs, Tab } from 'react-bootstrap';
import { Tabs, Tab, Container } from 'react-bootstrap';
import Meritocracy from 'Embark/contracts/Meritocracy';
import { getFormattedContributorList, getCurrentContributorData } from '../services/Meritocracy';
import { getFormattedContributorList, getCurrentContributorData, getAllPraises } from '../services/Meritocracy';
import './home.scss';
import Step1 from './Step1';
import Step2 from './Step2';
@ -10,7 +10,7 @@ import Loading from './Loading';
import Complete from './Complete';
import Error from './Error';
import Withdrawal from './Withdrawal';
import {sortByAlpha} from '../utils';
import {sortByAlpha, sortByAttribute} from '../utils';
/*
TODO:
- list praise for contributor
@ -33,7 +33,8 @@ class Home extends React.Component {
praise: '',
step: 'HOME',
checkbox: false,
tab: 'reward'
tab: 'reward',
praises: []
};
constructor(props) {
@ -53,6 +54,11 @@ class Home extends React.Component {
const currentContributor = await getCurrentContributorData();
this.setState({ busy: false, currentContributor, contributorList: contributorList.sort(sortByAlpha('label'))});
getAllPraises().then(praises => {
this.setState({praises: praises.sort(sortByAttribute('time'))});
});
} catch (error) {
this.setState({ errorMsg: error.message || error });
}
@ -188,6 +194,7 @@ class Home extends React.Component {
award,
currentContributor,
praise,
praises,
errorMsg,
step,
checkbox,
@ -230,7 +237,11 @@ class Home extends React.Component {
{step === 'COMPLETE' && <Complete onClick={this.moveStep('HOME')} />}
</Tab>
<Tab eventKey="wall" title="Wall">
<Container className="pt-4">
{praises.map((item, i) => <Praise key={i} individual={false} contributorList={contributorList} item={item} />)}
</Container>
</Tab>
<Tab eventKey="withdraw" title="Withdraw" className="withdraw-panel">
{step === 'HOME' && (
<Withdrawal

View File

@ -0,0 +1,34 @@
/* global web3 */
import React, {Fragment} from 'react';
import moment from 'moment';
import Address from './Address';
import { Row, Col } from 'react-bootstrap';
const Praise = ({contributorList, item, individual}) => {
const name = contributorList.find(x => x.value === item.author);
const date = moment.unix(item.time).fromNow();
return (
<Row>
<Col className="mb-4 text-muted">
{!item.praise && (
<Fragment>
{(name && name.label) || <Address value={item.author} compact={true} />} {individual ? "has sent you" : "sent"}{' '}
{web3.utils.fromWei(item.amount, 'ether')} SNT {!individual && <span>to {item.destination}</span>}, <small>{date}</small>
</Fragment>
)}
{item.praise && (
<Fragment>
{(name && name.label) || <Address value={item.author} compact={true} />}{!individual && <span> to {item.destination}</span>}, <small>{date}</small>
<div className="chatBubble p-3">
&quot;{item.praise}&quot;
<small className="float-right">{web3.utils.fromWei(item.amount, 'ether')} SNT</small>
</div>
</Fragment>
)}
</Col>
</Row>
);
};
export default Praise;

View File

@ -1,9 +1,7 @@
/* global web3 */
import React, { Fragment } from 'react';
import { Row, Col, Button, Container } from 'react-bootstrap';
import moment from 'moment';
import { Button, Container } from 'react-bootstrap';
import info from '../../images/red-info.svg';
import Address from './Address';
import Praise from './Praise';
import './withdrawal.scss';
@ -19,32 +17,7 @@ const Withdrawal = ({ totalReceived, allocation, onClick, contributorList, prais
<Container>
{praises &&
praises.map((item, i) => {
const name = contributorList.find(x => x.value === item.author);
const date = moment.unix(item.time).fromNow();
return (
<Row key={i}>
<Col className="mb-4 text-muted">
{!item.praise && (
<Fragment>
{(name && name.label) || <Address value={item.author} compact={true} />} has sent you{' '}
{web3.utils.fromWei(item.amount, 'ether')} SNT <small>{date}</small>
</Fragment>
)}
{item.praise && (
<Fragment>
{(name && name.label) || item.author}, <small>{date}</small>
<div className="chatBubble p-3">
&quot;{item.praise}&quot;
<small className="float-right">{web3.utils.fromWei(item.amount, 'ether')} SNT</small>
</div>
</Fragment>
)}
</Col>
</Row>
);
})}
praises.map((item, i) => <Praise key={i} individual={true} contributorList={contributorList} item={item} />)}
</Container>
<p className="text-center">
@ -57,7 +30,7 @@ const Withdrawal = ({ totalReceived, allocation, onClick, contributorList, prais
</Button>
</p>
{parseInt(allocation, 10) > 0 && (
{totalReceived !== '0' && parseInt(allocation, 10) > 0 && (
<div className="text-muted text-left border rounded p-2 mb-2 learn-more">
<img src={info} alt="" />
<p className="m-0 p-0">

View File

@ -1,6 +1,6 @@
.header {
.navbar-brand {
font-size: 22px;
font-size: 20px;
font-weight: bold;
}
}

View File

@ -7,8 +7,8 @@
border: none;
color: $dark;
border-bottom: 2px solid $dark;
font-size: 17px;
width: 48%;
font-size: 15px;
width: 32%;
text-align: center;
&.active {
@ -17,7 +17,7 @@
}
&+.nav-link {
margin-left: 4%;
margin-left: 2%;
}
}
}

View File

@ -140,6 +140,43 @@ export async function getContributorData(_address) {
return currentContributor;
}
export function getAllPraises() {
return new Promise(function(resolve) {
let praisesPromises = [];
let praiseNumPromises = [];
for(let i = 0; i < contributorList.length; i++){
praiseNumPromises.push(Meritocracy.methods.getStatusLength(contributorList[i].value).call());
}
Promise.all(praiseNumPromises).then(praiseNums => {
for(let i = 0; i < contributorList.length; i++){
let currPraises = [];
for(let j = 0; j < praiseNums[i]; j++){
currPraises.push(Meritocracy.methods.getStatus(contributorList[i].value, j).call());
}
praisesPromises.push(currPraises);
}
const allPromises = Promise.all(
praisesPromises.map(function(innerPromiseArray) {
return Promise.all(innerPromiseArray);
})
);
allPromises.then(praises => {
for(let i = 0; i < praises.length; i++){
praises[i] = praises[i].map(x => {
x.destination = contributorList[i].label;
return x;
});
}
resolve(praises.flat());
});
});
});
}
export async function getContributor(_address) {
const contributor = await Meritocracy.methods.contributors(_address).call();

View File

@ -12,6 +12,12 @@ export const sortByAttribute = field => (a, b) => {
return 0;
};
export const sortByAttributeDesc = field => (a, b) => {
if (a[field] < b[field]) return -1;
if (a[field] > b[field]) return 1;
return 0;
};
export const sortNullableArray = field => (a, b) => {
const a_field = a[field] || [];
const b_field = b[field] || [];