Merge pull request #13 from status-im/reward-screen
Reward and Withdraw UI changes + code format
|
@ -0,0 +1,274 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"standard",
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:unicorn/recommended",
|
||||
"prettier",
|
||||
"prettier/standard",
|
||||
"prettier/unicorn"
|
||||
],
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 2018,
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"__": true
|
||||
},
|
||||
"plugins": ["babel", "react", "prettier", "standard", "unicorn"],
|
||||
"rules": {
|
||||
"accessor-pairs": "error",
|
||||
"array-bracket-newline": "error",
|
||||
"array-bracket-spacing": ["error", "never"],
|
||||
"array-callback-return": "off",
|
||||
"array-element-newline": "off",
|
||||
"arrow-body-style": "off",
|
||||
"arrow-parens": "off",
|
||||
"arrow-spacing": [
|
||||
"error",
|
||||
{
|
||||
"after": true,
|
||||
"before": true
|
||||
}
|
||||
],
|
||||
"block-scoped-var": "error",
|
||||
"block-spacing": "error",
|
||||
"brace-style": "off",
|
||||
"callback-return": "off",
|
||||
"camelcase": "off",
|
||||
"capitalized-comments": "off",
|
||||
"class-methods-use-this": "off",
|
||||
"comma-dangle": "error",
|
||||
"comma-spacing": "off",
|
||||
"comma-style": ["error", "last"],
|
||||
"complexity": "error",
|
||||
"computed-property-spacing": ["error", "never"],
|
||||
"consistent-return": "off",
|
||||
"consistent-this": "off",
|
||||
"curly": "off",
|
||||
"default-case": "error",
|
||||
"dot-location": ["error", "property"],
|
||||
"dot-notation": "off",
|
||||
"eol-last": "error",
|
||||
"eqeqeq": "error",
|
||||
"for-direction": "error",
|
||||
"func-call-spacing": "error",
|
||||
"func-name-matching": "error",
|
||||
"func-names": "off",
|
||||
"func-style": "off",
|
||||
"function-paren-newline": "off",
|
||||
"generator-star-spacing": "error",
|
||||
"getter-return": "error",
|
||||
"global-require": "off",
|
||||
"guard-for-in": "off",
|
||||
"handle-callback-err": "off",
|
||||
"id-blacklist": "error",
|
||||
"id-length": "off",
|
||||
"id-match": "error",
|
||||
"indent": ["error", 2, {"SwitchCase": 1}],
|
||||
"indent-legacy": "off",
|
||||
"init-declarations": "off",
|
||||
"jsx-quotes": "error",
|
||||
"key-spacing": "off",
|
||||
"keyword-spacing": "off",
|
||||
"line-comment-position": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"lines-around-comment": "error",
|
||||
"lines-around-directive": "error",
|
||||
"max-depth": "error",
|
||||
"max-len": "off",
|
||||
"max-lines": "off",
|
||||
"max-params": "off",
|
||||
"max-statements": "off",
|
||||
"max-statements-per-line": "off",
|
||||
"multiline-ternary": "off",
|
||||
"new-parens": "off",
|
||||
"newline-after-var": "off",
|
||||
"newline-before-return": "off",
|
||||
"newline-per-chained-call": "off",
|
||||
"no-alert": "error",
|
||||
"no-array-constructor": "error",
|
||||
"no-await-in-loop": "error",
|
||||
"no-bitwise": "error",
|
||||
"no-buffer-constructor": "error",
|
||||
"no-caller": "error",
|
||||
"no-catch-shadow": "error",
|
||||
"no-confusing-arrow": "error",
|
||||
"no-console": "off",
|
||||
"no-continue": "off",
|
||||
"no-debugger": "warn",
|
||||
"no-div-regex": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-else-return": 2,
|
||||
"no-empty-function": "off",
|
||||
"no-eq-null": "error",
|
||||
"no-eval": "off",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-extra-label": "error",
|
||||
"no-extra-parens": "off",
|
||||
"no-floating-decimal": "error",
|
||||
"no-implicit-coercion": [
|
||||
"error",
|
||||
{
|
||||
"allow": ["!!"]
|
||||
}
|
||||
],
|
||||
"no-implicit-globals": "error",
|
||||
"no-implied-eval": "error",
|
||||
"no-inline-comments": "off",
|
||||
"no-inner-declarations": ["error", "functions"],
|
||||
"no-invalid-this": "off",
|
||||
"no-iterator": "error",
|
||||
"no-label-var": "error",
|
||||
"no-labels": "error",
|
||||
"no-lone-blocks": "error",
|
||||
"no-lonely-if": "off",
|
||||
"no-loop-func": "off",
|
||||
"no-magic-numbers": "off",
|
||||
"no-mixed-operators": "error",
|
||||
"no-mixed-requires": "error",
|
||||
"no-multi-assign": "error",
|
||||
"no-multi-spaces": "off",
|
||||
"no-multi-str": "error",
|
||||
"no-multiple-empty-lines": "error",
|
||||
"no-native-reassign": "error",
|
||||
"no-negated-condition": "off",
|
||||
"no-negated-in-lhs": "error",
|
||||
"no-nested-ternary": "error",
|
||||
"no-new": "error",
|
||||
"no-new-func": "error",
|
||||
"no-new-object": "error",
|
||||
"no-new-require": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-param-reassign": "off",
|
||||
"no-path-concat": "error",
|
||||
"no-plusplus": "off",
|
||||
"no-process-env": "off",
|
||||
"no-process-exit": "off",
|
||||
"no-proto": "error",
|
||||
"no-prototype-builtins": "off",
|
||||
"no-restricted-globals": "error",
|
||||
"no-restricted-imports": "error",
|
||||
"no-restricted-modules": "error",
|
||||
"no-restricted-properties": "error",
|
||||
"no-restricted-syntax": "error",
|
||||
"no-return-assign": "error",
|
||||
"no-return-await": "error",
|
||||
"no-script-url": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-sequences": "error",
|
||||
"no-shadow": "off",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-spaced-func": "error",
|
||||
"no-sync": "off",
|
||||
"no-tabs": "error",
|
||||
"no-template-curly-in-string": "error",
|
||||
"no-ternary": "off",
|
||||
"no-throw-literal": "error",
|
||||
"no-trailing-spaces": "off",
|
||||
"no-undef-init": "error",
|
||||
"no-undefined": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unneeded-ternary": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"no-use-before-define": "off",
|
||||
"no-useless-call": "off",
|
||||
"no-useless-computed-key": "error",
|
||||
"no-useless-concat": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-useless-escape": "off",
|
||||
"no-useless-rename": "error",
|
||||
"no-useless-return": "off",
|
||||
"no-var": "off",
|
||||
"no-void": "error",
|
||||
"no-warning-comments": "off",
|
||||
"no-whitespace-before-property": "error",
|
||||
"no-with": "error",
|
||||
"nonblock-statement-body-position": "error",
|
||||
"object-curly-newline": "off",
|
||||
"object-curly-spacing": ["off"],
|
||||
"object-property-newline": "off",
|
||||
"object-shorthand": "off",
|
||||
"one-var": "off",
|
||||
"one-var-declaration-per-line": "off",
|
||||
"operator-assignment": "off",
|
||||
"operator-linebreak": "error",
|
||||
"padded-blocks": "off",
|
||||
"padding-line-between-statements": "error",
|
||||
"prefer-arrow-callback": "off",
|
||||
"prefer-const": "off",
|
||||
"prefer-destructuring": "off",
|
||||
"prefer-numeric-literals": "error",
|
||||
"prefer-promise-reject-errors": "error",
|
||||
"prefer-reflect": "off",
|
||||
"prefer-rest-params": "off",
|
||||
"prefer-spread": "off",
|
||||
"prefer-template": "off",
|
||||
"quote-props": "off",
|
||||
"quotes": "off",
|
||||
"radix": "error",
|
||||
"require-await": "off",
|
||||
"require-jsdoc": "off",
|
||||
"rest-spread-spacing": "error",
|
||||
"semi": ["error", "always"],
|
||||
"semi-spacing": [
|
||||
"error",
|
||||
{
|
||||
"after": true,
|
||||
"before": false
|
||||
}
|
||||
],
|
||||
"semi-style": ["error", "last"],
|
||||
"sort-imports": "off",
|
||||
"sort-keys": "off",
|
||||
"sort-vars": "off",
|
||||
"space-before-blocks": "off",
|
||||
"space-before-function-paren": "off",
|
||||
"space-in-parens": ["error", "never"],
|
||||
"space-infix-ops": "off",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": "off",
|
||||
"strict": "error",
|
||||
"switch-colon-spacing": "error",
|
||||
"symbol-description": "error",
|
||||
"template-curly-spacing": ["error", "never"],
|
||||
"template-tag-spacing": "error",
|
||||
"unicode-bom": ["error", "never"],
|
||||
"valid-jsdoc": "error",
|
||||
"vars-on-top": "off",
|
||||
"wrap-iife": "error",
|
||||
"wrap-regex": "error",
|
||||
"yield-star-spacing": "error",
|
||||
"yoda": ["error", "never"],
|
||||
"react/jsx-boolean-value": 0,
|
||||
"react/jsx-closing-bracket-location": 1,
|
||||
"react/jsx-curly-spacing": "off",
|
||||
"react/jsx-indent-props": [1, 2],
|
||||
"react/jsx-no-undef": 1,
|
||||
"react/jsx-uses-react": 1,
|
||||
"react/jsx-uses-vars": 1,
|
||||
"react/react-in-jsx-scope": 1,
|
||||
"react/prefer-es6-class": 1,
|
||||
"react/jsx-no-bind": "off",
|
||||
"unicorn/filename-case": "off"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
trailingComma: "none"
|
||||
printWidth: 120
|
||||
useTabs: false
|
||||
semi: true
|
||||
arrowParens: "avoid"
|
||||
tabWidth: 2
|
||||
endOfLine: "lf"
|
||||
singleQuote: true
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="9" height="16" viewBox="0 0 9 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.00687048 7.89841C0.027045 7.67387 0.122799 7.45524 0.292809 7.28566L7.30422 0.292065C7.6926 -0.0953277 8.3188 -0.0987967 8.70893 0.290334C9.09635 0.676771 9.0907 1.30894 8.70719 1.69148L2.38119 8.00141L8.70719 14.3114C9.09557 14.6987 9.09905 15.3234 8.70893 15.7125C8.32151 16.0989 7.68773 16.0933 7.30422 15.7108L0.292809 8.71716C0.0681878 8.49311 -0.0276859 8.18971 0.00687048 7.89841Z" fill="#4360DF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 559 B |
|
@ -0,0 +1,3 @@
|
|||
<svg width="9" height="16" viewBox="0 0 9 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.70719 8.71434L1.69578 15.7079C1.3074 16.0953 0.681196 16.0988 0.291074 15.7097C-0.0963472 15.3232 -0.0907033 14.6911 0.292809 14.3085L6.61881 7.99859L0.292809 1.68865C-0.0955705 1.30126 -0.0990483 0.676638 0.291074 0.287507C0.678495 -0.0989301 1.31227 -0.0933004 1.69578 0.289237L8.70719 7.28284C8.93181 7.50689 9.02769 7.81029 8.99313 8.10159C8.97295 8.32613 8.8772 8.54476 8.70719 8.71434Z" fill="#4360DF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 563 B |
After Width: | Height: | Size: 19 KiB |
|
@ -0,0 +1,3 @@
|
|||
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.49501 0.505054C1.22164 0.231688 0.778427 0.231688 0.505059 0.505054C0.231692 0.778421 0.231692 1.22164 0.505059 1.495L4.50506 5.495C4.77843 5.76837 5.22164 5.76837 5.49501 5.495L9.49501 1.495C9.76838 1.22164 9.76838 0.778421 9.49501 0.505054C9.22164 0.231688 8.77843 0.231688 8.50506 0.505054L4.99812 4.01199L1.49501 0.505054Z" fill="#4360DF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 498 B |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 5.8 KiB |
|
@ -0,0 +1,5 @@
|
|||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 16.3612C13 16.9134 12.5522 17.3612 12 17.3612C11.4478 17.3612 11 16.9134 11 16.3612V11.4403C11 11.4403 11 10.4099 12 10.4099C12.8345 10.4099 13 11.2942 13 11.4403V16.3612Z" fill="black"/>
|
||||
<path d="M13 8.4099C13 8.96214 12.5522 9.4099 12 9.4099C11.4478 9.4099 11 8.96214 11 8.4099C11 7.85765 11.4478 7.4099 12 7.4099C12.5522 7.4099 13 7.85765 13 8.4099Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 12.4099C2 17.9327 6.47705 22.4099 12 22.4099C17.5229 22.4099 22 17.9327 22 12.4099C22 6.88707 17.5229 2.4099 12 2.4099C6.47705 2.4099 2 6.88707 2 12.4099ZM20 12.4099C20 16.8282 16.4182 20.4099 12 20.4099C7.58179 20.4099 4 16.8282 4 12.4099C4 7.99156 7.58179 4.4099 12 4.4099C16.4182 4.4099 20 7.99156 20 12.4099Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 866 B |
|
@ -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 |
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.6868 9.90763C10.4882 9.97671 9.7371 9.69751 8.53839 9.76674C8.24109 9.78344 7.94584 9.82653 7.65613 9.8955C7.83306 7.67874 9.40193 5.73957 11.5353 5.61631C12.8445 5.54076 14.1531 6.34908 14.224 7.66127C14.2939 8.95099 13.3105 9.81381 11.6869 9.90747L11.6868 9.90763ZM8.46823 14.4419C7.21403 14.5128 5.96065 13.7561 5.89258 12.5281C5.82564 11.3211 6.7679 10.5136 8.32323 10.4259C9.47129 10.3612 10.191 10.6226 11.339 10.5577C11.6237 10.5421 11.9065 10.5018 12.1842 10.4372C12.015 12.5118 10.5121 14.3268 8.46823 14.4419ZM10 0.000161758C4.4771 -2.09028e-09 0 4.47703 0 10C0 15.523 4.4771 20 10 20C15.5229 20 20 15.5228 20 10C20 4.47719 15.5229 0 10 0" fill="#4360DF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 823 B |
|
@ -2,11 +2,11 @@
|
|||
<head>
|
||||
<title>Meritocracy</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/images/app/images/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/images/app/images/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/images/app/images/favicon/favicon-16x16.png">
|
||||
<link rel="manifest" href="/images/app/images/favicon/site.webmanifest">
|
||||
<link rel="mask-icon" href="/images/app/images/favicon/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="images/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="images/favicon/favicon-150x150.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="images/favicon/favicon-16x16.png">
|
||||
<link rel="manifest" href="images/favicon/site.webmanifest">
|
||||
<link rel="mask-icon" href="images/favicon/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
</head>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import React, { Component } from 'react';
|
||||
import info from '../../images/info.svg';
|
||||
import downArrow from '../../images/down-arrow.svg';
|
||||
|
||||
class Allocation extends Component {
|
||||
state = {
|
||||
showHelp: false
|
||||
};
|
||||
|
||||
handleClickHelp = e => {
|
||||
e.preventDefault();
|
||||
this.setState(prevState => ({ showHelp: !prevState.showHelp }));
|
||||
};
|
||||
|
||||
render() {
|
||||
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>
|
||||
</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>
|
||||
</div>
|
||||
)}
|
||||
<p className="allocation mb-0">
|
||||
{value} <span className="text-muted">SNT</span>
|
||||
</p>
|
||||
<p className="text-muted">Available</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Allocation;
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import CompleteIcon from '../../images/complete.png';
|
||||
import { Button } from 'react-bootstrap';
|
||||
|
||||
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>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Complete;
|
|
@ -0,0 +1,71 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import Select from 'react-select';
|
||||
import { Form } from 'react-bootstrap';
|
||||
import Allocation from './Allocation';
|
||||
import statusLogo from '../../images/status-logo.svg';
|
||||
|
||||
import './contributor-selector.scss';
|
||||
|
||||
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
|
||||
}) => (
|
||||
<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-2">
|
||||
<div className="plus-5" title="Add +5" onClick={onClickPlus5}>
|
||||
<img alt="+5" src={statusLogo} />
|
||||
<span>+5</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-10">
|
||||
<Select
|
||||
isMulti
|
||||
value={selectedContributors}
|
||||
onChange={onSelectContributor}
|
||||
options={contributorList.sort(sortByAlpha)}
|
||||
placeholder="Choose Contributor(s)..."
|
||||
className="mb-2 contributorSelector"
|
||||
theme={theme => ({
|
||||
...theme,
|
||||
borderRadius: '4px',
|
||||
border: 'none',
|
||||
padding: '10px',
|
||||
colors: {
|
||||
...theme.colors,
|
||||
neutral0: '#EEF2F5',
|
||||
neutral10: '#EEF2F5'
|
||||
},
|
||||
spacing: {
|
||||
...theme.spacing,
|
||||
controlHeight: 50
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-2 p-0">
|
||||
<Form.Control type="number" step="1" onChange={onChangeAward} value={award} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default ContributorSelection;
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import ErrorIcon from '../../images/error.png';
|
||||
import { Button } from 'react-bootstrap';
|
||||
|
||||
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>
|
||||
</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,14 +1,15 @@
|
|||
/*global web3*/
|
||||
import React, {Fragment} from 'react';
|
||||
import {Row, Col, Alert, Button, Container, Form, Tabs, Tab} from 'react-bootstrap';
|
||||
import NumericInput from 'react-numeric-input';
|
||||
import Select from 'react-select';
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import { Tabs, Tab } from 'react-bootstrap';
|
||||
import Meritocracy from 'Embark/contracts/Meritocracy';
|
||||
|
||||
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: {
|
||||
|
@ -31,7 +30,10 @@ class Home extends React.Component {
|
|||
status: []
|
||||
},
|
||||
award: 0,
|
||||
praise: ''
|
||||
praise: '',
|
||||
step: 'HOME',
|
||||
checkbox: false,
|
||||
tab: 'reward'
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -50,54 +52,76 @@ 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 });
|
||||
}
|
||||
}
|
||||
|
||||
handleContributorSelection(_selectedContributors) {
|
||||
this.setState({ selectedContributors: _selectedContributors });
|
||||
this.setState({ selectedContributors: _selectedContributors }, () => {
|
||||
this._setAward(this.state.award);
|
||||
});
|
||||
}
|
||||
|
||||
handleAwardChange(_amount) {
|
||||
const { currentContributor: {allocation}, selectedContributors} = this.state;
|
||||
|
||||
const maxAllocation = allocation / selectedContributors.length;
|
||||
const award = (_amount <= maxAllocation ? _amount : maxAllocation );
|
||||
this.setState({award});
|
||||
handleAwardChange(e) {
|
||||
if (e.target.value.trim() === '') {
|
||||
this.setState({ award: '' });
|
||||
return;
|
||||
}
|
||||
this._setAward(e.target.value);
|
||||
}
|
||||
|
||||
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 > 0 ? Math.floor(allocation / selectedContributors.length) : 0;
|
||||
const award = _amount <= maxAllocation ? _amount : maxAllocation;
|
||||
|
||||
this.setState({ award });
|
||||
};
|
||||
|
||||
handlePraiseChange(e) {
|
||||
this.setState({ praise: e.target.value });
|
||||
}
|
||||
|
||||
resetUIFields(){
|
||||
handleCheckbox = () => {
|
||||
this.setState(prevState => ({ checkbox: !prevState.checkbox }));
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
// TODO some sanity checks
|
||||
if(award <= 0) {
|
||||
this.setState({errorMsg: 'amount must be more than 0'});
|
||||
return;
|
||||
}
|
||||
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);
|
||||
|
@ -108,126 +132,123 @@ class Home extends React.Component {
|
|||
}
|
||||
|
||||
try {
|
||||
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 });
|
||||
this.resetUIFields();
|
||||
const currentContributor = await getCurrentContributorData();
|
||||
this.setState({currentContributor});
|
||||
} catch(e) {
|
||||
this.setState({errorMsg: 'tx failed? got enough tokens to award?'});
|
||||
console.error(e);
|
||||
} finally {
|
||||
this.setState({busy: false});
|
||||
this.setState({ currentContributor });
|
||||
this.moveStep('COMPLETE')();
|
||||
} 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, errorMsg: '' });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedContributors, contributorList, award, currentContributor, praise, busy, errorMsg } = this.state;
|
||||
const {
|
||||
selectedContributors,
|
||||
contributorList,
|
||||
award,
|
||||
currentContributor,
|
||||
praise,
|
||||
errorMsg,
|
||||
step,
|
||||
checkbox,
|
||||
tab
|
||||
} = this.state;
|
||||
|
||||
const maxAllocation = selectedContributors.length ? currentContributor.allocation / selectedContributors.length : 0;
|
||||
if (errorMsg) return <Error title="Error" message={errorMsg} onClick={this.moveStep('HOME')} />;
|
||||
|
||||
const orderedContributors = contributorList.sort((a,b) => {
|
||||
if (a.label < b.label) return -1;
|
||||
if (a.label > b.label) return 1;
|
||||
return 0;
|
||||
});
|
||||
return (
|
||||
<Fragment>
|
||||
<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}
|
||||
onChangeAward={this.handleAwardChange}
|
||||
onSelectContributor={this.handleContributorSelection}
|
||||
onClickPlus5={this.handlePlus5}
|
||||
contributorList={contributorList}
|
||||
selectedContributors={selectedContributors}
|
||||
award={award}
|
||||
isChecked={checkbox}
|
||||
onClickCheckbox={this.handleCheckbox}
|
||||
onClickNext={this.moveStep('PRAISE')}
|
||||
/>
|
||||
)}
|
||||
|
||||
return (<Fragment>
|
||||
{errorMsg && <Alert variant="danger">{errorMsg}</Alert>}
|
||||
{busy && <p>Working...</p>}
|
||||
{step === 'PRAISE' && (
|
||||
<Step2
|
||||
selectedContributors={selectedContributors}
|
||||
award={award}
|
||||
praise={praise}
|
||||
onChangeNote={this.handlePraiseChange}
|
||||
onClickBack={this.moveStep('HOME')}
|
||||
onClickAward={this.awardTokens}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Tabs defaultActiveKey="reward" className="home-tabs mb-3">
|
||||
<Tab eventKey="reward" title="Reward" className="reward-panel">
|
||||
<div className="text-center p-4">
|
||||
<p className="text-muted">Reward Status contributors for all the times they impressed you.</p>
|
||||
<p className="allocation mb-0">{currentContributor.allocation} <span className="text-muted">SNT</span></p>
|
||||
<p className="text-muted">Available</p>
|
||||
</div>
|
||||
{step === 'BUSY' && <Loading />}
|
||||
|
||||
<Select
|
||||
isMulti
|
||||
value={selectedContributors}
|
||||
onChange={this.handleContributorSelection}
|
||||
options={orderedContributors}
|
||||
placeholder="Choose Contributor(s)..."
|
||||
isDisabled={busy}
|
||||
className="mb-2"
|
||||
/>
|
||||
{step === 'COMPLETE' && <Complete onClick={this.moveStep('HOME')} />}
|
||||
</Tab>
|
||||
|
||||
{selectedContributors.length === 0 && <Alert variant="secondary">
|
||||
Please select one or more contributors
|
||||
</Alert>}
|
||||
<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}
|
||||
/>
|
||||
)}
|
||||
|
||||
<NumericInput mobile step={5} min={0} max={maxAllocation} onChange={this.handleAwardChange} value={award}
|
||||
disabled={busy} className="form-control mb-2"/>
|
||||
{step === 'BUSY' && <Loading />}
|
||||
|
||||
<Form>
|
||||
<Form.Control disabled={busy} placeholder="Enter your praise..." onChange={this.handlePraiseChange}
|
||||
value={praise}/>
|
||||
</Form>
|
||||
<p className="text-center"> Total Awarding: {award * selectedContributors.length} SNT </p>
|
||||
<p className="text-center"><Button disabled={busy} variant="outline-primary" onClick={this.awardTokens}>Award</Button></p>
|
||||
</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>
|
||||
</Tabs>
|
||||
</Fragment>);
|
||||
{step === 'COMPLETE' && <Complete onClick={this.moveStep('HOME')} />}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
import './loading.scss';
|
||||
import spinner from '../../images/spinner.png';
|
||||
|
||||
const Loading = () => (
|
||||
<div className="busy text-center mt-5 pt-5">
|
||||
<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;
|
|
@ -0,0 +1,52 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import arrowRight from '../../images/arrow-right.svg';
|
||||
import ContributorSelection from './ContributorSelection';
|
||||
import { Button, Form } from 'react-bootstrap';
|
||||
|
||||
const Step1 = ({
|
||||
allocation,
|
||||
onChangeAward,
|
||||
onSelectContributor,
|
||||
onClickPlus5,
|
||||
contributorList,
|
||||
selectedContributors,
|
||||
award,
|
||||
isChecked,
|
||||
onClickCheckbox,
|
||||
onClickNext
|
||||
}) => (
|
||||
<Fragment>
|
||||
<ContributorSelection
|
||||
allocation={allocation}
|
||||
onChangeAward={onChangeAward}
|
||||
onSelectContributor={onSelectContributor}
|
||||
onClickPlus5={onClickPlus5}
|
||||
contributorList={contributorList}
|
||||
selectedContributors={selectedContributors}
|
||||
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>
|
||||
|
||||
<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;
|
|
@ -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>
|
||||
|
||||
{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">
|
||||
Your budget wasn’t fully 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;
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
@import "../../css/variable-overrides";
|
||||
|
||||
.contributorSelector > div:first-child,
|
||||
.contributorSelector input {
|
||||
border-color: #EEF2F5 !important;
|
||||
}
|
||||
|
||||
.contributorSelector > div:nth-child(3){
|
||||
background: #FFFFFF !important;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.plus-5 {
|
||||
width: 45px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
span {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: 10px;
|
||||
font-size: 12px;
|
||||
color: $dark;
|
||||
|
||||
}
|
||||
}
|
|
@ -23,11 +23,40 @@
|
|||
}
|
||||
|
||||
.reward-panel {
|
||||
padding-bottom: 70px;
|
||||
|
||||
.allocation {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.react-numeric-input {
|
||||
.TOC {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.small-text {
|
||||
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,11 @@
|
|||
.busy {
|
||||
img {
|
||||
-webkit-animation:spin 7s linear infinite;
|
||||
-moz-animation:spin 7s linear infinite;
|
||||
animation:spin 7s linear infinite;
|
||||
}
|
||||
@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
|
||||
@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
|
||||
@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ function getContributors () {
|
|||
return addresses;
|
||||
}
|
||||
|
||||
const OG_IPFS_HASH = '0x516d617555364a6b66767038756a44444259784b3553797a58325831635842616642344259584e6838514d4b6e31';
|
||||
const OG_IPFS_HASH = '0x516d5968703543324c75646d45333239436d41594671625767733869577036625177396355714576544742356163';
|
||||
|
||||
module.exports = {
|
||||
// default applies to all environments
|
||||
|
@ -197,7 +197,7 @@ module.exports = {
|
|||
"address": "0x744d70fdbe2ba4cf95131626614a1763df805b9e"
|
||||
},
|
||||
"Meritocracy": {
|
||||
"address": "0x3d8ec98c08b55ec42310aace562e077d784591d6"
|
||||
"address": "0x0c869a3f3e915b49727669b5ea28c2efdf9a0a66"
|
||||
}
|
||||
},
|
||||
deployment: {
|
||||
|
|
|
@ -255,7 +255,7 @@ contract Meritocracy {
|
|||
// Locals
|
||||
uint256 registryLength = registry.length;
|
||||
// Requirements
|
||||
require(block.timestamp >= lastForfeit + 1 weeks); // prevents admins accidently calling too quickly.
|
||||
require(block.timestamp >= lastForfeit + 6 days); // prevents admins accidently calling too quickly.
|
||||
// Body
|
||||
lastForfeit = block.timestamp;
|
||||
for (uint256 i = 0; i < registryLength; i++) { // should never be longer than maxContributors, see addContributor
|
||||
|
|
20
package.json
|
@ -4,11 +4,28 @@
|
|||
"description": "",
|
||||
"main": "Gruntfile.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"prettier": "prettier --write \"app/**/*.js\"",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"lint": "./node_modules/.bin/eslint app"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"homepage": "",
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.0.1",
|
||||
"eslint": "^5.9.0",
|
||||
"eslint-config-prettier": "^4.0.0",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
"eslint-plugin-import": "^2.16.0",
|
||||
"eslint-plugin-node": "^8.0.1",
|
||||
"eslint-plugin-prettier": "^3.0.1",
|
||||
"eslint-plugin-promise": "^4.0.1",
|
||||
"eslint-plugin-react": "^7.12.4",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"eslint-plugin-unicorn": "^7.1.0",
|
||||
"prettier": "^1.16.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.17",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.8.1",
|
||||
|
@ -16,6 +33,7 @@
|
|||
"bootstrap": "^4.3.1",
|
||||
"embark-solc": "^4.0.1",
|
||||
"embarkjs-connector-web3": "^4.0.0",
|
||||
"moment": "^2.24.0",
|
||||
"react": "^16.8.6",
|
||||
"react-bootstrap": "1.0.0-beta.6",
|
||||
"react-dom": "^16.8.6",
|
||||
|
|