feat: withdrawal screens + prettier
This commit is contained in:
parent
ae884d278c
commit
64fdef7697
|
@ -0,0 +1,8 @@
|
|||
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 14.3612C11 14.9135 10.5522 15.3612 10 15.3612C9.44775 15.3612 9 14.9135 9 14.3612V9.44031C9 9.44031 9 8.40991 10 8.40991C10.8345 8.40991 11 9.29419 11 9.44031V14.3612Z" fill="#FF2D55"/>
|
||||
<path d="M11 6.40991C11 6.96216 10.5522 7.40991 10 7.40991C9.44775 7.40991 9 6.96216 9 6.40991C9 5.85767 9.44775 5.40991 10 5.40991C10.5522 5.40991 11 5.85767 11 6.40991Z" fill="#FF2D55"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 10.4099C0 15.9327 4.47705 20.4099 10 20.4099C15.5229 20.4099 20 15.9327 20 10.4099C20 4.88708 15.5229 0.409912 10 0.409912C4.47705 0.409912 0 4.88708 0 10.4099ZM18 10.4099C18 14.8282 14.4182 18.4099 10 18.4099C5.58179 18.4099 2 14.8282 2 10.4099C2 5.99158 5.58179 2.40991 10 2.40991C14.4182 2.40991 18 5.99158 18 10.4099Z" fill="#FF2D55"/>
|
||||
<path d="M11 14.3612C11 14.9135 10.5522 15.3612 10 15.3612C9.44775 15.3612 9 14.9135 9 14.3612V9.44031C9 9.44031 9 8.40991 10 8.40991C10.8345 8.40991 11 9.29419 11 9.44031V14.3612Z" stroke="#FFEAEE"/>
|
||||
<path d="M11 6.40991C11 6.96216 10.5522 7.40991 10 7.40991C9.44775 7.40991 9 6.96216 9 6.40991C9 5.85767 9.44775 5.40991 10 5.40991C10.5522 5.40991 11 5.85767 11 6.40991Z" stroke="#FFEAEE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 10.4099C0 15.9327 4.47705 20.4099 10 20.4099C15.5229 20.4099 20 15.9327 20 10.4099C20 4.88708 15.5229 0.409912 10 0.409912C4.47705 0.409912 0 4.88708 0 10.4099ZM18 10.4099C18 14.8282 14.4182 18.4099 10 18.4099C5.58179 18.4099 2 14.8282 2 10.4099C2 5.99158 5.58179 2.40991 10 2.40991C14.4182 2.40991 18 5.99158 18 10.4099Z" stroke="#FFEAEE"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -1,11 +1,11 @@
|
|||
/*global web3*/
|
||||
import React, {Fragment} from 'react';
|
||||
import {HashRouter, Route, Redirect, Switch} from "react-router-dom";
|
||||
import React from 'react';
|
||||
import { HashRouter, Route, Redirect, Switch } from 'react-router-dom';
|
||||
import ThemeProvider from 'react-bootstrap/ThemeProvider';
|
||||
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
|
||||
import {isAdmin} from './services/Meritocracy';
|
||||
import { isAdmin } from './services/Meritocracy';
|
||||
import Header from './components/Header';
|
||||
import Home from './components/Home';
|
||||
import Admin from './components/Admin';
|
||||
|
@ -14,7 +14,6 @@ const MAINNET = 1;
|
|||
const TESTNET = 3;
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
state = {
|
||||
error: null,
|
||||
loading: true,
|
||||
|
@ -22,52 +21,57 @@ class App extends React.Component {
|
|||
};
|
||||
|
||||
componentDidMount() {
|
||||
EmbarkJS.onReady(async (err) => {
|
||||
EmbarkJS.onReady(async err => {
|
||||
if (err) {
|
||||
return this.setState({error: err.message || err});
|
||||
return this.setState({ error: err.message || err });
|
||||
}
|
||||
|
||||
const netId = await web3.eth.net.getId();
|
||||
if (EmbarkJS.environment === 'testnet' && netId !== TESTNET) {
|
||||
return this.setState({error: 'Please connect to Ropsten'});
|
||||
return this.setState({ error: 'Please connect to Ropsten' });
|
||||
}
|
||||
if (EmbarkJS.environment === 'livenet' && netId !== MAINNET) {
|
||||
return this.setState({error: 'Please connect to Mainnet'});
|
||||
return this.setState({ error: 'Please connect to Mainnet' });
|
||||
}
|
||||
|
||||
const isUserAdmin = await isAdmin(web3.eth.defaultAccount);
|
||||
|
||||
this.setState({loading: false, isUserAdmin})
|
||||
this.setState({ loading: false, isUserAdmin });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {error, loading, isUserAdmin} = this.state;
|
||||
const { error, loading, isUserAdmin } = this.state;
|
||||
|
||||
if (error) {
|
||||
return (<div>
|
||||
<div>Something went wrong connecting to Ethereum. Please make sure you have a node running or are using Metamask
|
||||
to connect to the Ethereum network:
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
Something went wrong connecting to Ethereum. Please make sure you have a node running or are using Metamask
|
||||
to connect to the Ethereum network:
|
||||
</div>
|
||||
<div>{error}</div>
|
||||
</div>
|
||||
<div>{error}</div>
|
||||
</div>);
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <p>Loading, please wait</p>;
|
||||
}
|
||||
|
||||
return (<HashRouter>
|
||||
<ThemeProvider prefixes={{ btn: 'my-btn' }}>
|
||||
<Header isUserAdmin={isUserAdmin}/>
|
||||
<Switch>
|
||||
<Route exact path="/" component={Home}/>
|
||||
{isUserAdmin && <Route exact path="/admin" component={Admin}/>}
|
||||
return (
|
||||
<HashRouter>
|
||||
<ThemeProvider prefixes={{ btn: 'my-btn' }}>
|
||||
<Header isUserAdmin={isUserAdmin} />
|
||||
<Switch>
|
||||
<Route exact path="/" component={Home} />
|
||||
{isUserAdmin && <Route exact path="/admin" component={Admin} />}
|
||||
|
||||
<Redirect to="/404"/>
|
||||
</Switch>
|
||||
</ThemeProvider>
|
||||
</HashRouter>);
|
||||
<Redirect to="/404" />
|
||||
</Switch>
|
||||
</ThemeProvider>
|
||||
</HashRouter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function compactAddress(addr) {
|
||||
return addr.substring(0, 6) + '...' + addr.substring(38);
|
||||
}
|
||||
|
||||
class Address extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
addressHovered: false,
|
||||
fixed: false
|
||||
};
|
||||
}
|
||||
|
||||
mouseOverAddress = () => {
|
||||
this.setState({ addressHovered: true });
|
||||
};
|
||||
|
||||
mouseOutAddress = () => {
|
||||
this.setState({ addressHovered: false });
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
this.setState({ fixed: !this.state.fixed });
|
||||
};
|
||||
|
||||
render() {
|
||||
const address =
|
||||
this.props.compact || (!this.state.fixed && !this.state.addressHovered)
|
||||
? compactAddress(this.props.value)
|
||||
: this.props.value;
|
||||
return (
|
||||
<span
|
||||
title={this.props.value}
|
||||
onClick={this.handleClick}
|
||||
onMouseOver={this.mouseOverAddress}
|
||||
onMouseOut={this.mouseOutAddress}
|
||||
>
|
||||
{address}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Address.defaultProps = {
|
||||
compact: false
|
||||
};
|
||||
|
||||
Address.propTypes = {
|
||||
value: PropTypes.string,
|
||||
compact: PropTypes.bool
|
||||
};
|
||||
|
||||
export default Address;
|
|
@ -1,13 +1,12 @@
|
|||
/*global web3*/
|
||||
import React, {Fragment} from 'react';
|
||||
import {Button, Form, Alert, ListGroup, OverlayTrigger, Tooltip, Modal} from 'react-bootstrap';
|
||||
import React, { Fragment } from 'react';
|
||||
import { Button, Form, Alert, ListGroup, OverlayTrigger, Tooltip, Modal } from 'react-bootstrap';
|
||||
import ValidatedForm from 'react-validation/build/form';
|
||||
import Input from 'react-validation/build/input';
|
||||
import {required, isAddress} from '../validators';
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faTrash} from "@fortawesome/free-solid-svg-icons";
|
||||
import { required, isAddress } from '../validators';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import {addContributor, getFormattedContributorList, removeContributor} from '../services/Meritocracy';
|
||||
import { addContributor, getFormattedContributorList, removeContributor } from '../services/Meritocracy';
|
||||
|
||||
import './admin.scss';
|
||||
|
||||
|
@ -27,121 +26,139 @@ class Admin extends React.Component {
|
|||
try {
|
||||
const contributorList = await getFormattedContributorList();
|
||||
|
||||
this.setState({busy: false, contributorList});
|
||||
} catch (e) {
|
||||
this.setState({errorMsg: e.message || e});
|
||||
this.setState({ busy: false, contributorList });
|
||||
} catch (error) {
|
||||
this.setState({ errorMsg: error.message || error });
|
||||
}
|
||||
}
|
||||
|
||||
onChange = (name, e) => {
|
||||
this.setState({[name]: e.target.value});
|
||||
this.setState({ [name]: e.target.value });
|
||||
};
|
||||
|
||||
addContributor = async (e) => {
|
||||
addContributor = async e => {
|
||||
e.preventDefault();
|
||||
this.setState({busy: true, successMsg: ''});
|
||||
this.setState({ busy: true, successMsg: '' });
|
||||
try {
|
||||
await addContributor(this.state.contributorName, this.state.contributorAddress);
|
||||
|
||||
const contributorList = this.state.contributorList;
|
||||
contributorList.push({label: this.state.contributorName, value: this.state.contributorAddress});
|
||||
contributorList.push({ label: this.state.contributorName, value: this.state.contributorAddress });
|
||||
|
||||
this.setState({busy: false, successMsg: 'Contributor added!'});
|
||||
} catch (e) {
|
||||
this.setState({error: e.message || e, busy: false});
|
||||
this.setState({ busy: false, successMsg: 'Contributor added!' });
|
||||
} catch (error) {
|
||||
this.setState({ error: error.message || error, busy: false });
|
||||
}
|
||||
};
|
||||
|
||||
removeContributor = (e, contributorIndex) => {
|
||||
e.preventDefault();
|
||||
this.setState({focusedContributorIndex: contributorIndex, showDeleteModal: true});
|
||||
this.setState({ focusedContributorIndex: contributorIndex, showDeleteModal: true });
|
||||
};
|
||||
|
||||
doRemove = async () => {
|
||||
const idx = this.state.focusedContributorIndex;
|
||||
this.setState({focusedContributorIndex: -1, showDeleteModal: false, busy: true});
|
||||
this.setState({ focusedContributorIndex: -1, showDeleteModal: false, busy: true });
|
||||
try {
|
||||
await removeContributor(this.state.contributorList[idx].value);
|
||||
|
||||
const contributorList = this.state.contributorList;
|
||||
contributorList.splice(idx, 1);
|
||||
|
||||
this.setState({contributorList, busy: false, successMsg: 'Contributor removed!'});
|
||||
} catch (e) {
|
||||
this.setState({error: e.message || e, busy: false});
|
||||
this.setState({ contributorList, busy: false, successMsg: 'Contributor removed!' });
|
||||
} catch (error) {
|
||||
this.setState({ error: error.message || error, busy: false });
|
||||
}
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
this.setState({showDeleteModal: false});
|
||||
this.setState({ showDeleteModal: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
const {contributorAddress, contributorName, error, busy, contributorList, successMsg, focusedContributorIndex} = this.state;
|
||||
const {
|
||||
contributorAddress,
|
||||
contributorName,
|
||||
error,
|
||||
busy,
|
||||
contributorList,
|
||||
successMsg,
|
||||
focusedContributorIndex
|
||||
} = this.state;
|
||||
const currentContributor = focusedContributorIndex > -1 ? contributorList[focusedContributorIndex] : {};
|
||||
|
||||
return (<Fragment>
|
||||
<h2>Admin Panel</h2>
|
||||
{error && <Alert variant="danger">{error}</Alert>}
|
||||
{successMsg && <Alert variant="success">{successMsg}</Alert>}
|
||||
{busy && <Alert variant="primary">Working...</Alert>}
|
||||
<h3>Add a contributor</h3>
|
||||
<ValidatedForm onSubmit={(e) => this.addContributor(e)}>
|
||||
<Form.Group controlId="formContributor">
|
||||
<Form.Label>Contributor name</Form.Label>
|
||||
<Input type="text" placeholder="Name" value={contributorName}
|
||||
onChange={(e) => this.onChange('contributorName', e)}
|
||||
className="form-control"
|
||||
validations={[required]}/>
|
||||
</Form.Group>
|
||||
return (
|
||||
<Fragment>
|
||||
<h2>Admin Panel</h2>
|
||||
{error && <Alert variant="danger">{error}</Alert>}
|
||||
{successMsg && <Alert variant="success">{successMsg}</Alert>}
|
||||
{busy && <Alert variant="primary">Working...</Alert>}
|
||||
<h3>Add a contributor</h3>
|
||||
<ValidatedForm onSubmit={e => this.addContributor(e)}>
|
||||
<Form.Group controlId="formContributor">
|
||||
<Form.Label>Contributor name</Form.Label>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
value={contributorName}
|
||||
onChange={e => this.onChange('contributorName', e)}
|
||||
className="form-control"
|
||||
validations={[required]}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="formAddress">
|
||||
<Form.Label>Contributor address</Form.Label>
|
||||
<Input type="text" placeholder="0x" value={contributorAddress}
|
||||
onChange={(e) => this.onChange('contributorAddress', e)}
|
||||
className="form-control"
|
||||
validations={[required, isAddress]}/>
|
||||
</Form.Group>
|
||||
<Button variant="primary" onClick={(e) => this.addContributor(e)}>Add</Button>
|
||||
</ValidatedForm>
|
||||
<h3>Contributor List</h3>
|
||||
<ListGroup>
|
||||
{contributorList.map((contributor, idx) => (
|
||||
<ListGroup.Item key={contributor.value} action className="contributor-item">
|
||||
<span className="font-weight-bold">{contributor.label}:</span> {contributor.value}
|
||||
|
||||
<div className="contributor-controls float-right">
|
||||
<OverlayTrigger placement="top"
|
||||
overlay={
|
||||
<Tooltip>
|
||||
Delete contributor
|
||||
</Tooltip>
|
||||
}>
|
||||
<FontAwesomeIcon icon={faTrash} className="text-danger icon" onClick={(e) => this.removeContributor(e, idx)}/>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
</ListGroup.Item>
|
||||
))}
|
||||
</ListGroup>
|
||||
|
||||
<Modal show={this.state.showDeleteModal} onHide={this.handleClose}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Are you sure you want to remove this contributor?</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<p>Name: {currentContributor.label}</p>
|
||||
<p>Address: {currentContributor.value}</p>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={this.handleClose}>
|
||||
Cancel
|
||||
<Form.Group controlId="formAddress">
|
||||
<Form.Label>Contributor address</Form.Label>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="0x"
|
||||
value={contributorAddress}
|
||||
onChange={e => this.onChange('contributorAddress', e)}
|
||||
className="form-control"
|
||||
validations={[required, isAddress]}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Button variant="primary" onClick={e => this.addContributor(e)}>
|
||||
Add
|
||||
</Button>
|
||||
<Button variant="danger" onClick={this.doRemove}>
|
||||
Remove
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</Fragment>);
|
||||
</ValidatedForm>
|
||||
<h3>Contributor List</h3>
|
||||
<ListGroup>
|
||||
{contributorList.map((contributor, idx) => (
|
||||
<ListGroup.Item key={contributor.value} action className="contributor-item">
|
||||
<span className="font-weight-bold">{contributor.label}:</span> {contributor.value}
|
||||
<div className="contributor-controls float-right">
|
||||
<OverlayTrigger placement="top" overlay={<Tooltip>Delete contributor</Tooltip>}>
|
||||
<FontAwesomeIcon
|
||||
icon={faTrash}
|
||||
className="text-danger icon"
|
||||
onClick={e => this.removeContributor(e, idx)}
|
||||
/>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
</ListGroup.Item>
|
||||
))}
|
||||
</ListGroup>
|
||||
|
||||
<Modal show={this.state.showDeleteModal} onHide={this.handleClose}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Are you sure you want to remove this contributor?</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<p>Name: {currentContributor.label}</p>
|
||||
<p>Address: {currentContributor.value}</p>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={this.handleClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="danger" onClick={this.doRemove}>
|
||||
Remove
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,40 +1,44 @@
|
|||
import React, {Component} from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import info from '../../images/info.svg';
|
||||
import downArrow from '../../images/down-arrow.svg';
|
||||
|
||||
import "./allocation.scss";
|
||||
|
||||
class Allocation extends Component {
|
||||
state = {
|
||||
showHelp: false
|
||||
}
|
||||
};
|
||||
|
||||
handleClickHelp = (e) => {
|
||||
handleClickHelp = e => {
|
||||
e.preventDefault();
|
||||
this.setState(prevState => ({ showHelp: !prevState.showHelp }));
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {value} = this.props;
|
||||
const {showHelp} = this.state;
|
||||
|
||||
const { value } = this.props;
|
||||
const { showHelp } = this.state;
|
||||
|
||||
return (
|
||||
<div className="text-center p-4 allocation">
|
||||
<p className="text-muted mb-2">Reward Status contributors for all the times they impressed you.</p>
|
||||
<p className="mb-2">
|
||||
<a href="#" onClick={this.handleClickHelp}>Learn more <img src={downArrow} alt="" className="ml-2" /></a>
|
||||
<a href="#" onClick={this.handleClickHelp}>
|
||||
Learn more <img src={downArrow} alt="" className="ml-2" />
|
||||
</a>
|
||||
</p>
|
||||
{showHelp && (
|
||||
<div className="text-muted text-left border rounded p-2 mb-2 learn-more">
|
||||
<img src={info} alt="" />
|
||||
<p className="m-0 p-0">
|
||||
Status Meritocracy is an SNT Reward System that allows a Contributor in the registry to
|
||||
award allocated SNT, along with praise, to other Contributors.<br />
|
||||
<a href="https://github.com/status-im/meritocracy/blob/master/register.md">Register</a> to
|
||||
receive a budget and participate.</p>
|
||||
Status Meritocracy is an SNT Reward System that allows a Contributor in the registry to award allocated
|
||||
SNT, along with praise, to other Contributors.
|
||||
<br />
|
||||
<a href="https://github.com/status-im/meritocracy/blob/master/register.md">Register</a> to receive a
|
||||
budget and participate.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<p className="allocation mb-0">{value} <span className="text-muted">SNT</span></p>
|
||||
<p className="allocation mb-0">
|
||||
{value} <span className="text-muted">SNT</span>
|
||||
</p>
|
||||
<p className="text-muted">Available</p>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
import React from 'react';
|
||||
import CompleteIcon from '../../images/complete.png';
|
||||
import {Button} from 'react-bootstrap';
|
||||
import { Button } from 'react-bootstrap';
|
||||
|
||||
const Complete = ({onClick}) => (
|
||||
const Complete = ({ onClick }) => (
|
||||
<div className="text-center mt-5 pt-5">
|
||||
<img src={CompleteIcon} alt="" width="160" height="160" className="mt-5" />
|
||||
<h4 className="text-center pr-5 pl-5 mt-3">Thank you</h4>
|
||||
<p className="text-muted">Your SNT has been awarded.</p>
|
||||
<p><Button onClick={onClick} variant="link">Back</Button></p>
|
||||
<p>
|
||||
<Button onClick={onClick} variant="link">
|
||||
Back
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
export default Complete;
|
||||
|
|
|
@ -1,25 +1,31 @@
|
|||
import React, {Fragment} from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import Select from 'react-select';
|
||||
import {Form} from 'react-bootstrap';
|
||||
import { Form } from 'react-bootstrap';
|
||||
import Allocation from './Allocation';
|
||||
import statusLogo from '../../images/status-logo.svg';
|
||||
|
||||
import "./contributor-selector.scss";
|
||||
import './contributor-selector.scss';
|
||||
|
||||
const sortByAlpha = (a,b) => {
|
||||
const sortByAlpha = (a, b) => {
|
||||
if (a.label < b.label) return -1;
|
||||
if (a.label > b.label) return 1;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
const ContributorSelection = ({allocation, contributorList, selectedContributors, onSelectContributor, onChangeAward, onClickPlus5, award}) => (
|
||||
const ContributorSelection = ({
|
||||
allocation,
|
||||
contributorList,
|
||||
selectedContributors,
|
||||
onSelectContributor,
|
||||
onChangeAward,
|
||||
onClickPlus5,
|
||||
award
|
||||
}) => (
|
||||
<Fragment>
|
||||
<Allocation value={allocation - award * selectedContributors.length} />
|
||||
<div className="container">
|
||||
<div className="row mb-2">
|
||||
<div className="col-10 label">
|
||||
Enter contributors and award SNT
|
||||
</div>
|
||||
<div className="col-10 label">Enter contributors and award SNT</div>
|
||||
<div className="col-2">
|
||||
<div className="plus-5" title="Add +5" onClick={onClickPlus5}>
|
||||
<img alt="+5" src={statusLogo} />
|
||||
|
@ -37,33 +43,28 @@ const ContributorSelection = ({allocation, contributorList, selectedContributors
|
|||
options={contributorList.sort(sortByAlpha)}
|
||||
placeholder="Choose Contributor(s)..."
|
||||
className="mb-2 contributorSelector"
|
||||
theme={(theme) => ({
|
||||
theme={theme => ({
|
||||
...theme,
|
||||
borderRadius: '4px',
|
||||
border: 'none',
|
||||
padding: '10px',
|
||||
colors: {
|
||||
...theme.colors,
|
||||
...theme.colors,
|
||||
neutral0: '#EEF2F5',
|
||||
neutral10: '#EEF2F5',
|
||||
neutral10: '#EEF2F5'
|
||||
},
|
||||
spacing: {
|
||||
...theme.spacing,
|
||||
controlHeight: 50,
|
||||
controlHeight: 50
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-2 p-0">
|
||||
<Form.Control
|
||||
type="number"
|
||||
step="1"
|
||||
onChange={onChangeAward}
|
||||
value={award}
|
||||
/>
|
||||
<Form.Control type="number" step="1" onChange={onChangeAward} value={award} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
import React from 'react';
|
||||
import ErrorIcon from '../../images/error.png';
|
||||
import {Button} from 'react-bootstrap';
|
||||
import { Button } from 'react-bootstrap';
|
||||
|
||||
const Error = ({onClick, title, message}) => (
|
||||
const Error = ({ onClick, title, message }) => (
|
||||
<div className="text-center mt-5 pt-5">
|
||||
<img src={ErrorIcon} alt="" width="160" height="160" className="mt-5" />
|
||||
<h4 className="text-center pr-5 pl-5 mt-3">{title}</h4>
|
||||
<p className="text-muted">{message}</p>
|
||||
<p><Button onClick={onClick} variant="link">Back</Button></p>
|
||||
<p>
|
||||
<Button onClick={onClick} variant="link">
|
||||
Back
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
export default Error;
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
import React from 'react'
|
||||
import {Navbar, Nav} from 'react-bootstrap';
|
||||
import React from 'react';
|
||||
import { Navbar, Nav } from 'react-bootstrap';
|
||||
|
||||
import './header.scss'
|
||||
import './header.scss';
|
||||
import logo from '../../images/logo.png';
|
||||
|
||||
const Header = ({isUserAdmin}) => (<Navbar expand="lg" className="header border-bottom mb-3">
|
||||
<Navbar.Brand href="/#/"><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>}
|
||||
const Header = ({ isUserAdmin }) => (
|
||||
<Navbar expand="lg" className="header border-bottom mb-3">
|
||||
<Navbar.Brand href="/#/">
|
||||
<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>
|
||||
)}
|
||||
</Navbar>
|
||||
);
|
||||
|
||||
export default Header
|
||||
export default Header;
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
const History = ({value}) => (
|
||||
<div className="text-center p-4">
|
||||
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Allocation;
|
|
@ -1,14 +1,15 @@
|
|||
/*global web3*/
|
||||
import React, {Fragment} from 'react';
|
||||
import {Row, Col, Alert, Button, Container, Form, Tabs, Tab} from 'react-bootstrap';
|
||||
import React, { Fragment } from 'react';
|
||||
import { Tabs, Tab } from 'react-bootstrap';
|
||||
import Meritocracy from 'Embark/contracts/Meritocracy';
|
||||
import arrowLeft from '../../images/arrow-left.svg';
|
||||
import {getFormattedContributorList, getCurrentContributorData} from '../services/Meritocracy';
|
||||
import { getFormattedContributorList, getCurrentContributorData } from '../services/Meritocracy';
|
||||
import './home.scss';
|
||||
import Step1 from './Step1';
|
||||
import Step2 from './Step2';
|
||||
import Loading from './Loading';
|
||||
import Complete from './Complete';
|
||||
import Error from './Error';
|
||||
import Withdrawal from './Withdrawal';
|
||||
|
||||
/*
|
||||
TODO:
|
||||
|
@ -17,10 +18,8 @@ TODO:
|
|||
*/
|
||||
|
||||
class Home extends React.Component {
|
||||
|
||||
state = {
|
||||
errorMsg: null,
|
||||
busy: true,
|
||||
selectedContributors: [],
|
||||
contributorList: [],
|
||||
currentContributor: {
|
||||
|
@ -32,8 +31,9 @@ class Home extends React.Component {
|
|||
},
|
||||
award: 0,
|
||||
praise: '',
|
||||
step: 'ERROR',
|
||||
step: 'HOME',
|
||||
checkbox: false,
|
||||
tab: 'reward'
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -52,9 +52,9 @@ class Home extends React.Component {
|
|||
|
||||
const currentContributor = await getCurrentContributorData();
|
||||
|
||||
this.setState({busy: false, currentContributor, contributorList});
|
||||
} catch (e) {
|
||||
this.setState({errorMsg: e.message || e});
|
||||
this.setState({ busy: false, currentContributor, contributorList });
|
||||
} catch (error) {
|
||||
this.setState({ errorMsg: error.message || error });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,8 +65,8 @@ class Home extends React.Component {
|
|||
}
|
||||
|
||||
handleAwardChange(e) {
|
||||
if(e.target.value.trim() === "") {
|
||||
this.setState({award: ""});
|
||||
if (e.target.value.trim() === '') {
|
||||
this.setState({ award: '' });
|
||||
return;
|
||||
}
|
||||
this._setAward(e.target.value);
|
||||
|
@ -74,49 +74,54 @@ class Home extends React.Component {
|
|||
|
||||
handlePlus5 = () => {
|
||||
this._setAward(this.state.award + 5);
|
||||
}
|
||||
|
||||
_setAward = (value) => {
|
||||
let _amount = parseInt(value, 10);
|
||||
if(_amount < 0 || isNaN(_amount)) _amount = 0;
|
||||
|
||||
const { currentContributor: {allocation}, selectedContributors} = this.state;
|
||||
const maxAllocation = selectedContributors.length ? Math.floor(allocation / selectedContributors.length) : 0;
|
||||
const award = (_amount <= maxAllocation ? _amount : maxAllocation );
|
||||
};
|
||||
|
||||
this.setState({award});
|
||||
}
|
||||
_setAward = value => {
|
||||
let _amount = parseInt(value, 10);
|
||||
if (_amount < 0 || isNaN(_amount)) _amount = 0;
|
||||
|
||||
const {
|
||||
currentContributor: { allocation },
|
||||
selectedContributors
|
||||
} = this.state;
|
||||
const maxAllocation = selectedContributors.length > 0 ? Math.floor(allocation / selectedContributors.length) : 0;
|
||||
const award = _amount <= maxAllocation ? _amount : maxAllocation;
|
||||
|
||||
this.setState({ award });
|
||||
};
|
||||
|
||||
handlePraiseChange(e) {
|
||||
this.setState({ praise: e.target.value });
|
||||
}
|
||||
|
||||
handleCheckbox = (e) => {
|
||||
handleCheckbox = () => {
|
||||
this.setState(prevState => ({ checkbox: !prevState.checkbox }));
|
||||
}
|
||||
};
|
||||
|
||||
resetUIFields(){
|
||||
resetUIFields() {
|
||||
this.setState({
|
||||
praise: '',
|
||||
selectedContributors: [],
|
||||
errorMsg: '',
|
||||
award: 0
|
||||
award: 0,
|
||||
checkbox: false
|
||||
});
|
||||
}
|
||||
|
||||
async awardTokens(e) {
|
||||
const {award, selectedContributors, praise} = this.state;
|
||||
async awardTokens() {
|
||||
const { award, selectedContributors, praise } = this.state;
|
||||
|
||||
this.moveStep('BUSY');
|
||||
this.moveStep('BUSY')();
|
||||
|
||||
let addresses = selectedContributors.map(a => a.value);
|
||||
|
||||
const sntAmount = web3.utils.toWei(award.toString(), "ether");
|
||||
const sntAmount = web3.utils.toWei(award.toString(), 'ether');
|
||||
|
||||
let toSend;
|
||||
switch(addresses.length) {
|
||||
|
||||
switch (addresses.length) {
|
||||
case 0:
|
||||
this.setState({errorMsg: 'No Contributor Selected'});
|
||||
this.setState({ errorMsg: 'No Contributor Selected' });
|
||||
return;
|
||||
case 1:
|
||||
toSend = Meritocracy.methods.award(addresses[0], sntAmount, praise);
|
||||
|
@ -127,69 +132,74 @@ class Home extends React.Component {
|
|||
}
|
||||
|
||||
try {
|
||||
const estimatedGas = await toSend.estimateGas({from: web3.eth.defaultAccount});
|
||||
const receipt = await toSend.send({from: web3.eth.defaultAccount, gas: estimatedGas + 1000});
|
||||
const estimatedGas = await toSend.estimateGas({ from: web3.eth.defaultAccount });
|
||||
await toSend.send({ from: web3.eth.defaultAccount, gas: estimatedGas + 1000 });
|
||||
this.resetUIFields();
|
||||
const currentContributor = await getCurrentContributorData();
|
||||
this.setState({currentContributor});
|
||||
this.setState({ currentContributor });
|
||||
this.moveStep('COMPLETE')();
|
||||
} catch(e) {
|
||||
this.setState({errorMsg: 'tx failed? got enough tokens to award?'});
|
||||
console.error(e);
|
||||
} finally {
|
||||
|
||||
} catch (error) {
|
||||
this.setState({ errorMsg: 'tx failed? got enough tokens to award?' });
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async withdrawTokens(e) {
|
||||
const {currentContributor} = this.state;
|
||||
async withdrawTokens() {
|
||||
const { currentContributor } = this.state;
|
||||
|
||||
if (currentContributor.received === 0) {
|
||||
this.setState({errorMsg: 'can only call withdraw when you have tokens'});
|
||||
this.setState({ errorMsg: 'can only call withdraw when you have tokens' });
|
||||
return;
|
||||
}
|
||||
|
||||
if ( currentContributor.allocation > 0 ) {
|
||||
this.setState({errorMsg: 'you must allocate all your tokens'});
|
||||
if (currentContributor.allocation > 0) {
|
||||
this.setState({ errorMsg: 'you must allocate all your tokens' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.moveStep('BUSY')();
|
||||
|
||||
const toSend = Meritocracy.methods.withdraw();
|
||||
|
||||
try {
|
||||
this.setState({busy: true});
|
||||
this.setState({ busy: true });
|
||||
|
||||
const estimatedGas = await toSend.estimateGas({from: web3.eth.defaultAccount});
|
||||
const receipt = await toSend.send({from: web3.eth.defaultAccount, gas: estimatedGas + 1000});
|
||||
const estimatedGas = await toSend.estimateGas({ from: web3.eth.defaultAccount });
|
||||
await toSend.send({ from: web3.eth.defaultAccount, gas: estimatedGas + 1000 });
|
||||
|
||||
const currentContributor = await getCurrentContributorData();
|
||||
this.setState({currentContributor});
|
||||
|
||||
} catch(e) {
|
||||
this.setState({errorMsg: 'tx failed? Did you allocate all your tokens first?'});
|
||||
console.error(e);
|
||||
} finally {
|
||||
this.setState({busy: false});
|
||||
this.setState({ currentContributor });
|
||||
|
||||
this.moveStep('COMPLETE')();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.setState({ errorMsg: 'tx failed? Did you allocate all your tokens first?' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
moveStep = nexStep => () => {
|
||||
this.setState({step: nexStep});
|
||||
}
|
||||
|
||||
this.setState({ step: nexStep, errorMsg: '' });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedContributors, contributorList, award, currentContributor, praise, busy, errorMsg, step, checkbox } = this.state;
|
||||
const {
|
||||
selectedContributors,
|
||||
contributorList,
|
||||
award,
|
||||
currentContributor,
|
||||
praise,
|
||||
errorMsg,
|
||||
step,
|
||||
checkbox,
|
||||
tab
|
||||
} = this.state;
|
||||
|
||||
if(errorMsg) return <Error title="Error" value={errorMsg} />;
|
||||
if (errorMsg) return <Error title="Error" message={errorMsg} onClick={this.moveStep('HOME')} />;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Tabs defaultActiveKey="reward" className="home-tabs mb-3">
|
||||
<Tabs className="home-tabs mb-3" activeKey={tab} onSelect={tab => this.setState({ tab })}>
|
||||
<Tab eventKey="reward" title="Reward" className="reward-panel">
|
||||
|
||||
{step === 'HOME' && (
|
||||
<Step1
|
||||
allocation={currentContributor.allocation}
|
||||
|
@ -206,54 +216,35 @@ class Home extends React.Component {
|
|||
)}
|
||||
|
||||
{step === 'PRAISE' && (
|
||||
<div>
|
||||
<p className="text-center mt-5 text-muted">Research shows that a note of praise and learning how much our work helped others, increases motivation.</p>
|
||||
<p className="mb-0">
|
||||
<span className="font-weight-bold">{ selectedContributors.map(x => x.label).join(', ') }</span>
|
||||
<span className="float-right text-muted">SNT <b>{award * selectedContributors.length}</b></span>
|
||||
</p>
|
||||
<Form>
|
||||
<Form.Label className="small-text">Add note</Form.Label>
|
||||
<Form.Control disabled={busy} as="textarea" rows="5" onChange={this.handlePraiseChange}
|
||||
value={praise} className="p-2"/>
|
||||
</Form>
|
||||
<div className="fixed-bottom bg-white">
|
||||
<Button onClick={this.moveStep('HOME')} variant="link"><img src={arrowLeft} alt="" className="mr-2" /> Back</Button>
|
||||
<Button disabled={busy} variant="primary" className="float-right mr-2 mb-2" onClick={this.awardTokens}>Award</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Step2
|
||||
selectedContributors={selectedContributors}
|
||||
award={award}
|
||||
praise={praise}
|
||||
onChangeNote={this.handlePraiseChange}
|
||||
onClickBack={this.moveStep('HOME')}
|
||||
onClickAward={this.awardTokens}
|
||||
/>
|
||||
)}
|
||||
|
||||
{ step === 'BUSY' && <Loading /> }
|
||||
{ step === 'COMPLETE' && <Complete onClick={this.moveStep('HOME')} /> }
|
||||
{ step === 'ERROR' && <Error onClick={this.moveStep('PRAISE')} title="Error" message="Your transaction could not be processed" /> }
|
||||
{step === 'BUSY' && <Loading />}
|
||||
|
||||
{step === 'COMPLETE' && <Complete onClick={this.moveStep('HOME')} />}
|
||||
</Tab>
|
||||
|
||||
<Tab eventKey="withdraw" title="Withdraw">
|
||||
<p>Your Total Received Kudos: {currentContributor.totalReceived || 0} SNT</p>
|
||||
<p>Your Total Forfeited Kudos: {currentContributor.totalForfeited || 0} SNT</p>
|
||||
|
||||
<h4>Your Kudos History</h4>
|
||||
<p>Your Received Kudos: <b>{currentContributor.received} SNT</b></p>
|
||||
|
||||
<p className="text-center">
|
||||
<Button variant="outline-primary" onClick={this.withdrawTokens} disabled={busy}>
|
||||
Withdraw
|
||||
</Button>
|
||||
</p>
|
||||
|
||||
<Container>
|
||||
<Row>
|
||||
{currentContributor.praises && currentContributor.praises.map((item, i) => {
|
||||
const name = options.find(x => x.value === item.author);
|
||||
return <Col key={i}>{(name && name.label) || item.author} has sent
|
||||
you {web3.utils.fromWei(item.amount, "ether")} SNT {item.praise && "\"" + item.praise + "\""}</Col>;
|
||||
})}
|
||||
</Row>
|
||||
</Container>
|
||||
|
||||
<Tab eventKey="withdraw" title="Withdraw" className="withdraw-panel">
|
||||
{step === 'HOME' && (
|
||||
<Withdrawal
|
||||
onClick={this.withdrawTokens}
|
||||
totalReceived={currentContributor.totalReceived}
|
||||
allocation={currentContributor.allocation}
|
||||
contributorList={contributorList}
|
||||
praises={currentContributor.praises}
|
||||
/>
|
||||
)}
|
||||
|
||||
{step === 'BUSY' && <Loading />}
|
||||
|
||||
{step === 'COMPLETE' && <Complete onClick={this.moveStep('HOME')} />}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Fragment>
|
||||
|
|
|
@ -7,6 +7,6 @@ const Loading = () => (
|
|||
<img src={spinner} alt="" className="mt-5" />
|
||||
<h5 className="text-muted text-center pr-5 pl-5">Waiting for the confirmation from miners</h5>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
export default Loading;
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
import React, {Fragment} from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import arrowRight from '../../images/arrow-right.svg';
|
||||
import ContributorSelection from './ContributorSelection';
|
||||
import {Button, Form} from 'react-bootstrap';
|
||||
import { Button, Form } from 'react-bootstrap';
|
||||
|
||||
const Step1 = ({allocation, onChangeAward, onSelectContributor, onClickPlus5, contributorList, selectedContributors, award, isChecked, onClickCheckbox, onClickNext}) => (
|
||||
<Fragment>
|
||||
<ContributorSelection
|
||||
const Step1 = ({
|
||||
allocation,
|
||||
onChangeAward,
|
||||
onSelectContributor,
|
||||
onClickPlus5,
|
||||
contributorList,
|
||||
selectedContributors,
|
||||
award,
|
||||
isChecked,
|
||||
onClickCheckbox,
|
||||
onClickNext
|
||||
}) => (
|
||||
<Fragment>
|
||||
<ContributorSelection
|
||||
allocation={allocation}
|
||||
onChangeAward={onChangeAward}
|
||||
onSelectContributor={onSelectContributor}
|
||||
|
@ -15,14 +26,27 @@ const Step1 = ({allocation, onChangeAward, onSelectContributor, onClickPlus5, co
|
|||
award={award}
|
||||
/>
|
||||
|
||||
<Form.Group>
|
||||
<Form.Check type="checkbox" className="TOC pl-5 pr-2 mt-4" checked={isChecked} onChange={onClickCheckbox} label="I understand that I only receive rewards if I spend my entire reward budget." />
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
className="TOC pl-5 pr-2 mt-4"
|
||||
checked={isChecked}
|
||||
onChange={onClickCheckbox}
|
||||
label="I understand that I only receive rewards if I spend my entire reward budget."
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<div className="fixed-bottom bg-white">
|
||||
<Button disabled={selectedContributors.length === 0 || !(award > 0) || !isChecked} onClick={onClickNext} variant="link" className="float-right p-3">Next <img src={arrowRight} alt="" className="ml-2" /></Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
<div className="fixed-bottom bg-white">
|
||||
<Button
|
||||
disabled={selectedContributors.length === 0 || !(award > 0) || !isChecked}
|
||||
onClick={onClickNext}
|
||||
variant="link"
|
||||
className="float-right p-3"
|
||||
>
|
||||
Next <img src={arrowRight} alt="" className="ml-2" />
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default Step1;
|
||||
export default Step1;
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import React from 'react';
|
||||
import { Button, Form } from 'react-bootstrap';
|
||||
import arrowLeft from '../../images/arrow-left.svg';
|
||||
|
||||
const Step2 = ({ selectedContributors, award, praise, onChangeNote, onClickBack, onClickAward }) => (
|
||||
<div>
|
||||
<p className="text-center mt-5 text-muted">
|
||||
Research shows that a note of praise and learning how much our work helped others, increases motivation.
|
||||
</p>
|
||||
<p className="mb-0">
|
||||
<span className="font-weight-bold">{selectedContributors.map(x => x.label).join(', ')}</span>
|
||||
<span className="float-right text-muted">
|
||||
SNT <b>{award * selectedContributors.length}</b>
|
||||
</span>
|
||||
</p>
|
||||
<Form>
|
||||
<Form.Label className="small-text">Add note</Form.Label>
|
||||
<Form.Control as="textarea" rows="5" onChange={onChangeNote} value={praise} className="p-2" />
|
||||
</Form>
|
||||
<div className="fixed-bottom bg-white">
|
||||
<Button onClick={onClickBack} variant="link">
|
||||
<img src={arrowLeft} alt="" className="mr-2" /> Back
|
||||
</Button>
|
||||
<Button variant="primary" className="float-right mr-2 mb-2" onClick={onClickAward}>
|
||||
Award
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Step2;
|
|
@ -0,0 +1,72 @@
|
|||
/* global web3 */
|
||||
import React, { Fragment } from 'react';
|
||||
import { Row, Col, Button, Container } from 'react-bootstrap';
|
||||
import moment from 'moment';
|
||||
import info from '../../images/red-info.svg';
|
||||
import Address from './Address';
|
||||
|
||||
import './withdrawal.scss';
|
||||
|
||||
const Withdrawal = ({ totalReceived, allocation, onClick, contributorList, praises }) => (
|
||||
<Fragment>
|
||||
<div className="text-center p-4">
|
||||
<p className="text-muted mb-0 mt-5">You have been awarded</p>
|
||||
<p className="awarded mb-0">
|
||||
{totalReceived || 0} <span className="text-muted">SNT</span>
|
||||
</p>
|
||||
<p className="text-muted">Available for withdraw</p>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
"{item.praise}"
|
||||
<small className="float-right">{web3.utils.fromWei(item.amount, 'ether')} SNT</small>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</Container>
|
||||
|
||||
<p className="text-center">
|
||||
<Button
|
||||
variant={allocation !== '0' || totalReceived === '0' ? 'secondary' : 'primary'}
|
||||
onClick={onClick}
|
||||
disabled={allocation !== '0' || totalReceived === '0'}
|
||||
>
|
||||
Withdraw
|
||||
</Button>
|
||||
</p>
|
||||
|
||||
{allocation !== '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">
|
||||
Your budget wasn’t fullly rewarded to others. Note that you can only withdraw your own reward if you’ve spend
|
||||
your full budget to reward others.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default Withdrawal;
|
|
@ -1,15 +0,0 @@
|
|||
.learn-more {
|
||||
display: flex;
|
||||
|
||||
img {
|
||||
width: 25px;
|
||||
margin: 0 10px 0 0;
|
||||
object-fit: contain;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
p {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
}
|
|
@ -37,3 +37,26 @@
|
|||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.withdraw-panel {
|
||||
.awarded {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.learn-more {
|
||||
display: flex;
|
||||
|
||||
img {
|
||||
width: 25px;
|
||||
margin: 0 10px 0 0;
|
||||
object-fit: contain;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
p {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
.chatBubble {
|
||||
background: #ECEFFC;
|
||||
border-radius: 8px;
|
||||
}
|
|
@ -1,49 +1,49 @@
|
|||
const contributors = [
|
||||
{ 'label' : 'Andreas S.', 'value' : '0x4923121411e884a4af66ec025712eba600a782d3' },
|
||||
{ 'label' : 'andrey.dev', 'value' : '0xA4EcA293cb578a68b190e3e07c2B170dc753fe44' },
|
||||
{ 'label' : 'Anna', 'value': '0x2e1ce0f514387a188f4aeff4ceb6c2c0dea66ca7'},
|
||||
{ 'label' : 'barry', 'value' : '0xa46b0546481a04b7de049a8a20f8a9b2b2c5cc05' },
|
||||
{ 'label' : 'BrianXV', 'value' : '0x03b832b3fa819d7a4b6c819e4df1e60a173e739a' },
|
||||
{ 'label' : 'cammellos', 'value' : '0xd0ec8a940fe9712c1521c2190f41604ecaa7ec9e' },
|
||||
{ 'label' : 'Carl', 'value': '0x9fb937ab76b68dae29e3de20cf797d44f00e6410'},
|
||||
{ 'label' : 'ceri', 'value' : '0x68f47e153e1aa7d6529e078feff86eada87ddee3' },
|
||||
{ 'label' : 'Dani', 'value' : '0x89c010bc7085eb150b66582f13681f9e36904bea' },
|
||||
{ 'label' : 'dmitryn', 'value' : '0x6b0d7ba67aa3d84122749dc7906b8e7f25ed1af8' },
|
||||
{ 'label' : 'gravityblast', 'value' : '0xb5a2c17c7fd72070fcf078bb8458f2f595441066' },
|
||||
{ 'label' : 'guylouis.stateofus.eth', 'value' : '0x6913f3bdbb7c303977d6244c0e0071b4ebc6f359' },
|
||||
{ 'label' : 'Hester', 'value' : '0x8c4f71b3cf6a76de2cc239a6fa84e1a80e589598' },
|
||||
{ 'label' : 'Hutch', 'value' : '0x34a4b73100d11815ee4bb0ebcc86ba5824b12134' },
|
||||
{ 'label' : 'igor.stateofus.eth', 'value' : '0xff91043525391cd3a316450bc80ef874fb0ef446' },
|
||||
{ 'label' : 'jakubgs.eth', 'value' : '0x9b64770c9485A5188D238C305E53627A67C05D7D'},
|
||||
{ 'label' : 'Jinho', 'value' : '0x7407bF49004ee99d9B2caA2fb90B476bfF2DbCaf' },
|
||||
{ 'label' : 'Jonathan Barker', 'value' : '0xf23d05F375A8367b150f7Ad1A37DFd9E3c35eE56' },
|
||||
{ 'label' : 'Jonathan Rainville', 'value' : '0x9ce0056c5fc6bb9459a4dcfa35eaad8c1fee5ce9' },
|
||||
{ 'label' : 'Jonny Z', 'value' : '0xa40b07ac80d1f89b233b74e78d254c90906c33ee' },
|
||||
{ 'label' : 'Julien', 'value' : '0x6c618ddbf53aa9540c279e3670d4d26fb367fd4e' },
|
||||
{ 'label' : 'Maciej', 'value' : '0x227612e69b1d06250e7035c1c12840561ebf3c56' },
|
||||
{ 'label' : 'michele', 'value' : '0x658a1d2c105b35d9aaad38480dbbfe47b9054962' },
|
||||
{ 'label' : 'Nabil', 'value' : '0x528c9e62bb0e7083f4b42802297b38ba237776a0' },
|
||||
{ 'label' : 'Oskar', 'value' : '0x3fd6e2dfa535ce8b1e7eb7116a009eba3890b6bd' },
|
||||
{ 'label' : 'PascalPrecht', 'value' : '0x6f490165DdD8d604b52dB9D9BF9b63aE997DC11C' },
|
||||
{ 'label' : 'pedro.stateofus.eth', 'value' : '0x78EA50b13de394671474314aA261556717bF9185' },
|
||||
{ 'label' : 'Rachel', 'value' : '0x4b9ba5B0dEE90f5B84Bcbfbf921cF02e1C8da113' },
|
||||
{ 'label' : 'Rajanie', 'value' : '0x8af0d6fabc4a90ea0b95f80ab62beb816ed32a69' },
|
||||
{ 'label' : 'Ricardo Schmidt <3esmit>', 'value' : '0x3D597789ea16054a084ac84ce87F50df9198F415' },
|
||||
{ 'label' : 'Sergey', 'value' : '0xb9f914fe1c6edae2351fb42276868470083a3cd2' },
|
||||
{ 'label' : 'shemnon', 'value' : '0x82ad1b2419fd71dfe2d5db9b3c832c60ec96c53b' },
|
||||
{ 'label' : 'sonja.stateofus.eth', 'value' : '0xCF03738e9605C0B38cEAa7349bF6926463f01A25' },
|
||||
{ 'label' : 'Swader', 'value' : '0x9702797d92e2a06070b446e49a594a943686e28f' },
|
||||
{ 'label' : 'yenda', 'value' : '0xe829f7947175fe6a338344e70aa770a8c134372c' },
|
||||
{ 'label' : 'petty', 'value' : '0x2942577508e060ea092c0CD7802ae42c1CEA2BAe' },
|
||||
{ 'label' : 'chu', 'value' : '0xd21DB0e43048AcB94f428eD61dC244c82f1ff2a8' },
|
||||
{ 'label' : 'Yessin', 'value' : '0xbaba92b7822a56c05554ab5d1bc1d0b7e212499d' },
|
||||
{ 'label' : 'michaelb', 'value' : '0xdba0bade45727776bbb0d93176ee1ddba830f319' },
|
||||
{ 'label' : 'cryptowanderer', 'value' : '0x406abd306b633b6460666b4092784a3330370c7b' },
|
||||
{ 'label' : 'adam.stateofus.eth', 'value' : '0x074032269ca1775896c92304d45f80b5a67a5bcb' },
|
||||
{ 'label' : 'André Medeiros', 'value' : '0xf062E478870B17B55d1dC64888914B82aD9808B4' },
|
||||
{ 'label' : 'rramos', 'value' : '0xc379330ae48716b81d7411813c3250cd89271788' },
|
||||
{ 'label' : 'emizzle', 'value' : '0x91Ef8ef20Adf13E42757a3Ed6Ff2b1249bE15544' },
|
||||
{ 'label' : 'jason.stateofus.eth', 'value' : '0x4636fb2F6D1DC335EA655795064c2092c89148aB' }
|
||||
{ label: 'Andreas S.', value: '0x4923121411e884a4af66ec025712eba600a782d3' },
|
||||
{ label: 'andrey.dev', value: '0xA4EcA293cb578a68b190e3e07c2B170dc753fe44' },
|
||||
{ label: 'Anna', value: '0x2e1ce0f514387a188f4aeff4ceb6c2c0dea66ca7' },
|
||||
{ label: 'barry', value: '0xa46b0546481a04b7de049a8a20f8a9b2b2c5cc05' },
|
||||
{ label: 'BrianXV', value: '0x03b832b3fa819d7a4b6c819e4df1e60a173e739a' },
|
||||
{ label: 'cammellos', value: '0xd0ec8a940fe9712c1521c2190f41604ecaa7ec9e' },
|
||||
{ label: 'Carl', value: '0x9fb937ab76b68dae29e3de20cf797d44f00e6410' },
|
||||
{ label: 'ceri', value: '0x68f47e153e1aa7d6529e078feff86eada87ddee3' },
|
||||
{ label: 'Dani', value: '0x89c010bc7085eb150b66582f13681f9e36904bea' },
|
||||
{ label: 'dmitryn', value: '0x6b0d7ba67aa3d84122749dc7906b8e7f25ed1af8' },
|
||||
{ label: 'gravityblast', value: '0xb5a2c17c7fd72070fcf078bb8458f2f595441066' },
|
||||
{ label: 'guylouis.stateofus.eth', value: '0x6913f3bdbb7c303977d6244c0e0071b4ebc6f359' },
|
||||
{ label: 'Hester', value: '0x8c4f71b3cf6a76de2cc239a6fa84e1a80e589598' },
|
||||
{ label: 'Hutch', value: '0x34a4b73100d11815ee4bb0ebcc86ba5824b12134' },
|
||||
{ label: 'igor.stateofus.eth', value: '0xff91043525391cd3a316450bc80ef874fb0ef446' },
|
||||
{ label: 'jakubgs.eth', value: '0x9b64770c9485A5188D238C305E53627A67C05D7D' },
|
||||
{ label: 'Jinho', value: '0x7407bF49004ee99d9B2caA2fb90B476bfF2DbCaf' },
|
||||
{ label: 'Jonathan Barker', value: '0xf23d05F375A8367b150f7Ad1A37DFd9E3c35eE56' },
|
||||
{ label: 'Jonathan Rainville', value: '0x9ce0056c5fc6bb9459a4dcfa35eaad8c1fee5ce9' },
|
||||
{ label: 'Jonny Z', value: '0xa40b07ac80d1f89b233b74e78d254c90906c33ee' },
|
||||
{ label: 'Julien', value: '0x6c618ddbf53aa9540c279e3670d4d26fb367fd4e' },
|
||||
{ label: 'Maciej', value: '0x227612e69b1d06250e7035c1c12840561ebf3c56' },
|
||||
{ label: 'michele', value: '0x658a1d2c105b35d9aaad38480dbbfe47b9054962' },
|
||||
{ label: 'Nabil', value: '0x528c9e62bb0e7083f4b42802297b38ba237776a0' },
|
||||
{ label: 'Oskar', value: '0x3fd6e2dfa535ce8b1e7eb7116a009eba3890b6bd' },
|
||||
{ label: 'PascalPrecht', value: '0x6f490165DdD8d604b52dB9D9BF9b63aE997DC11C' },
|
||||
{ label: 'pedro.stateofus.eth', value: '0x78EA50b13de394671474314aA261556717bF9185' },
|
||||
{ label: 'Rachel', value: '0x4b9ba5B0dEE90f5B84Bcbfbf921cF02e1C8da113' },
|
||||
{ label: 'Rajanie', value: '0x8af0d6fabc4a90ea0b95f80ab62beb816ed32a69' },
|
||||
{ label: 'Ricardo Schmidt <3esmit>', value: '0x3D597789ea16054a084ac84ce87F50df9198F415' },
|
||||
{ label: 'Sergey', value: '0xb9f914fe1c6edae2351fb42276868470083a3cd2' },
|
||||
{ label: 'shemnon', value: '0x82ad1b2419fd71dfe2d5db9b3c832c60ec96c53b' },
|
||||
{ label: 'sonja.stateofus.eth', value: '0xCF03738e9605C0B38cEAa7349bF6926463f01A25' },
|
||||
{ label: 'Swader', value: '0x9702797d92e2a06070b446e49a594a943686e28f' },
|
||||
{ label: 'yenda', value: '0xe829f7947175fe6a338344e70aa770a8c134372c' },
|
||||
{ label: 'petty', value: '0x2942577508e060ea092c0CD7802ae42c1CEA2BAe' },
|
||||
{ label: 'chu', value: '0xd21DB0e43048AcB94f428eD61dC244c82f1ff2a8' },
|
||||
{ label: 'Yessin', value: '0xbaba92b7822a56c05554ab5d1bc1d0b7e212499d' },
|
||||
{ label: 'michaelb', value: '0xdba0bade45727776bbb0d93176ee1ddba830f319' },
|
||||
{ label: 'cryptowanderer', value: '0x406abd306b633b6460666b4092784a3330370c7b' },
|
||||
{ label: 'adam.stateofus.eth', value: '0x074032269ca1775896c92304d45f80b5a67a5bcb' },
|
||||
{ label: 'André Medeiros', value: '0xf062E478870B17B55d1dC64888914B82aD9808B4' },
|
||||
{ label: 'rramos', value: '0xc379330ae48716b81d7411813c3250cd89271788' },
|
||||
{ label: 'emizzle', value: '0x91Ef8ef20Adf13E42757a3Ed6Ff2b1249bE15544' },
|
||||
{ label: 'jason.stateofus.eth', value: '0x4636fb2F6D1DC335EA655795064c2092c89148aB' }
|
||||
];
|
||||
|
||||
module.exports = contributors;
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import App from './App';
|
||||
|
||||
import '../css/fonts/Inter/inter.css';
|
||||
import '../css/bootstrap-overrides.scss';
|
||||
import '../css/index.scss';
|
||||
|
||||
ReactDOM.render(
|
||||
<App/>,
|
||||
document.getElementById('app')
|
||||
);
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import App from './App';
|
||||
|
||||
import '../css/fonts/Inter/inter.css';
|
||||
import '../css/bootstrap-overrides.scss';
|
||||
import '../css/index.scss';
|
||||
|
||||
ReactDOM.render(<App />, document.querySelector('#app'));
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import Meritocracy from 'Embark/contracts/Meritocracy';
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
|
||||
|
||||
let contributorList;
|
||||
|
||||
export function addContributor(name, address) {
|
||||
|
@ -10,19 +9,19 @@ export function addContributor(name, address) {
|
|||
const mainAccount = web3.eth.defaultAccount;
|
||||
try {
|
||||
const list = await getContributorList();
|
||||
list.push({label: name, value: address});
|
||||
list.push({ label: name, value: address });
|
||||
|
||||
const newHash = await saveContributorList(list);
|
||||
|
||||
const addContributor = Meritocracy.methods.addContributor(address, web3.utils.toHex(newHash));
|
||||
let gas = await addContributor.estimateGas({from: mainAccount});
|
||||
const receipt = await addContributor.send({from: mainAccount, gas: gas + 1000});
|
||||
let gas = await addContributor.estimateGas({ from: mainAccount });
|
||||
const receipt = await addContributor.send({ from: mainAccount, gas: gas + 1000 });
|
||||
|
||||
resolve(receipt);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
const message = 'Error adding contributor';
|
||||
console.error(message);
|
||||
console.error(e);
|
||||
console.error(error);
|
||||
reject(message);
|
||||
}
|
||||
});
|
||||
|
@ -32,7 +31,7 @@ export function removeContributor(address) {
|
|||
return new Promise(async (resolve, reject) => {
|
||||
const mainAccount = web3.eth.defaultAccount;
|
||||
try {
|
||||
const registry = await Meritocracy.methods.getRegistry().call({from: mainAccount});
|
||||
const registry = await Meritocracy.methods.getRegistry().call({ from: mainAccount });
|
||||
let index = registry.indexOf(address);
|
||||
|
||||
const list = await getContributorList();
|
||||
|
@ -42,14 +41,14 @@ export function removeContributor(address) {
|
|||
const newHash = await saveContributorList(list);
|
||||
|
||||
const removeContributor = Meritocracy.methods.removeContributor(index, web3.utils.toHex(newHash));
|
||||
let gas = await removeContributor.estimateGas({from: mainAccount});
|
||||
const receipt = await removeContributor.send({from: mainAccount, gas: gas + 1000});
|
||||
let gas = await removeContributor.estimateGas({ from: mainAccount });
|
||||
const receipt = await removeContributor.send({ from: mainAccount, gas: gas + 1000 });
|
||||
|
||||
resolve(receipt);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
const message = 'Error removing contributor';
|
||||
console.error(message);
|
||||
console.error(e);
|
||||
console.error(error);
|
||||
reject(message);
|
||||
}
|
||||
});
|
||||
|
@ -66,10 +65,10 @@ export function getContributorList(hash) {
|
|||
const content = await EmbarkJS.Storage.get(hash);
|
||||
contributorList = JSON.parse(content);
|
||||
resolve(contributorList);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
const message = 'Error getting contributor file on IPFS';
|
||||
console.error(message);
|
||||
console.error(e);
|
||||
console.error(error);
|
||||
reject(message);
|
||||
}
|
||||
});
|
||||
|
@ -82,23 +81,27 @@ export async function getFormattedContributorList(hash) {
|
|||
let list = await getContributorList(hash);
|
||||
list = list.map(prepareOptions);
|
||||
|
||||
const registry = await Meritocracy.methods.getRegistry().call({from: mainAccount});
|
||||
list = list.filter(contributorData => registry.includes(contributorData.value) && contributorData.value !== mainAccount);
|
||||
const registry = await Meritocracy.methods.getRegistry().call({ from: mainAccount });
|
||||
list = list.filter(
|
||||
contributorData => registry.includes(contributorData.value) && contributorData.value !== mainAccount
|
||||
);
|
||||
|
||||
resolve(list);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
const message = 'Error getting formatted contributor file on IPFS';
|
||||
console.error(message);
|
||||
console.error(e);
|
||||
console.error(error);
|
||||
reject(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const prepareOptions = option => {
|
||||
if (option.value.match(/^0x[0-9A-Za-z]{40}$/)) { // Address
|
||||
if (option.value.match(/^0x[0-9A-Za-z]{40}$/)) {
|
||||
// Address
|
||||
option.value = web3.utils.toChecksumAddress(option.value);
|
||||
} else { // ENS Name
|
||||
} else {
|
||||
// ENS Name
|
||||
// TODO: resolve ENS names
|
||||
// EmbarkJS.Names.resolve("ethereum.eth").then(address => {
|
||||
// console.log("the address for ethereum.eth is: " + address);
|
||||
|
@ -107,12 +110,12 @@ const prepareOptions = option => {
|
|||
return option;
|
||||
};
|
||||
|
||||
export async function getCurrentContributorData(){
|
||||
export async function getCurrentContributorData() {
|
||||
const mainAccount = web3.eth.defaultAccount;
|
||||
const currentContributor = await getContributor(mainAccount);
|
||||
|
||||
let praises = [];
|
||||
for(let i = 0; i < currentContributor.praiseNum; i++){
|
||||
for (let i = 0; i < currentContributor.praiseNum; i++) {
|
||||
praises.push(Meritocracy.methods.getStatus(mainAccount, i).call());
|
||||
}
|
||||
|
||||
|
@ -121,13 +124,13 @@ export async function getCurrentContributorData(){
|
|||
}
|
||||
|
||||
const contribData = contributorList.find(x => x.value === mainAccount);
|
||||
if(contribData) currentContributor.name = contribData.label;
|
||||
if (contribData) currentContributor.name = contribData.label;
|
||||
|
||||
currentContributor.praises = await Promise.all(praises);
|
||||
currentContributor.allocation = web3.utils.fromWei(currentContributor.allocation, "ether");
|
||||
currentContributor.totalForfeited = web3.utils.fromWei(currentContributor.totalForfeited, "ether");
|
||||
currentContributor.totalReceived = web3.utils.fromWei(currentContributor.totalReceived, "ether");
|
||||
currentContributor.received = web3.utils.fromWei(currentContributor.received, "ether");
|
||||
currentContributor.allocation = web3.utils.fromWei(currentContributor.allocation, 'ether');
|
||||
currentContributor.totalForfeited = web3.utils.fromWei(currentContributor.totalForfeited, 'ether');
|
||||
currentContributor.totalReceived = web3.utils.fromWei(currentContributor.totalReceived, 'ether');
|
||||
currentContributor.received = web3.utils.fromWei(currentContributor.received, 'ether');
|
||||
|
||||
return currentContributor;
|
||||
}
|
||||
|
@ -144,10 +147,10 @@ export function saveContributorList(list) {
|
|||
contributorList = list;
|
||||
const newHash = await EmbarkJS.Storage.saveText(JSON.stringify(list));
|
||||
resolve(newHash);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
const message = 'Error saving contributor file on IPFS';
|
||||
console.error(message);
|
||||
console.error(e);
|
||||
console.error(error);
|
||||
reject(message);
|
||||
}
|
||||
});
|
||||
|
@ -158,12 +161,11 @@ export function isAdmin(address) {
|
|||
try {
|
||||
const result = await Meritocracy.methods.admins(address).call();
|
||||
resolve(result);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
const message = 'Could not get status of user';
|
||||
console.error(message);
|
||||
console.error(e);
|
||||
console.error(error);
|
||||
reject(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,60 +1,96 @@
|
|||
/*global Web3*/
|
||||
import React from 'react';
|
||||
import {Form} from 'react-bootstrap';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
export const required = (value) => {
|
||||
if (!value.toString().trim().length) {
|
||||
return <Form.Control.Feedback type="invalid" className="d-block">This field is required</Form.Control.Feedback>;
|
||||
export const required = value => {
|
||||
if (value.toString().trim().length === 0) {
|
||||
return (
|
||||
<Form.Control.Feedback type="invalid" className="d-block">
|
||||
This field is required
|
||||
</Form.Control.Feedback>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const isInteger = (value) => {
|
||||
export const isInteger = value => {
|
||||
value = parseFloat(value);
|
||||
if (!Number.isInteger(value)) {
|
||||
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be an integer</Form.Control.Feedback>;
|
||||
return (
|
||||
<Form.Control.Feedback type="invalid" className="d-block">
|
||||
This field needs to be an integer
|
||||
</Form.Control.Feedback>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const isNumber = (value) => {
|
||||
export const isNumber = value => {
|
||||
if (Number.isNaN(value)) {
|
||||
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be an number</Form.Control.Feedback>;
|
||||
return (
|
||||
<Form.Control.Feedback type="invalid" className="d-block">
|
||||
This field needs to be an number
|
||||
</Form.Control.Feedback>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const lowerThan = (max, value) => {
|
||||
if (value >= max) {
|
||||
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be lower than {max}</Form.Control.Feedback>;
|
||||
return (
|
||||
<Form.Control.Feedback type="invalid" className="d-block">
|
||||
This field needs to be lower than {max}
|
||||
</Form.Control.Feedback>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const lowerEqThan = (max, value) => {
|
||||
if (value > max) {
|
||||
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be lower or equal than {max}</Form.Control.Feedback>;
|
||||
return (
|
||||
<Form.Control.Feedback type="invalid" className="d-block">
|
||||
This field needs to be lower or equal than {max}
|
||||
</Form.Control.Feedback>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const higherThan = (min, value) => {
|
||||
if (value <= min) {
|
||||
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be higher than {min}</Form.Control.Feedback>;
|
||||
return (
|
||||
<Form.Control.Feedback type="invalid" className="d-block">
|
||||
This field needs to be higher than {min}
|
||||
</Form.Control.Feedback>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const higherEqThan = (min, value) => {
|
||||
if (value < min) {
|
||||
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be higher or equal than {min}</Form.Control.Feedback>;
|
||||
return (
|
||||
<Form.Control.Feedback type="invalid" className="d-block">
|
||||
This field needs to be higher or equal than {min}
|
||||
</Form.Control.Feedback>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const isAddress = (value) => {
|
||||
export const isAddress = value => {
|
||||
if (!Web3.utils.isAddress(value)) {
|
||||
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be a valid Ethereum address</Form.Control.Feedback>;
|
||||
return (
|
||||
<Form.Control.Feedback type="invalid" className="d-block">
|
||||
This field needs to be a valid Ethereum address
|
||||
</Form.Control.Feedback>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const isJSON = (value) => {
|
||||
export const isJSON = value => {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
} catch (e) {
|
||||
return <Form.Control.Feedback type="invalid" className="d-block">This field needs to be a valid JSON string</Form.Control.Feedback>;
|
||||
} catch (error) {
|
||||
return (
|
||||
<Form.Control.Feedback type="invalid" className="d-block">
|
||||
This field needs to be a valid JSON string
|
||||
</Form.Control.Feedback>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue