Add linting rules, with husky and prettier (#64)

* Add linting rules, with husky and prettier

* Update lint-staged path

* final
This commit is contained in:
Anshuman Verma 2019-02-18 23:25:21 +05:30 committed by Richard Ramos
parent 051fe36537
commit 28e3fb20d2
39 changed files with 6197 additions and 2576 deletions

569
.eslintrc
View File

@ -1,306 +1,273 @@
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"parser": "babel-eslint",
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2018,
"ecmaFeatures": {
"jsx": true
"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
}
},
"globals": {
"__": true
},
"plugins": [
"babel",
"react"
],
"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": "off",
"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": "off",
"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": [
"error",
"never"
],
"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", {
"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],
"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": ["error", "never"],
"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",
"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": 1
}
}
}
],
"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",
"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": 1
}
}

8
.prettierrc Normal file
View File

@ -0,0 +1,8 @@
trailingComma: "none"
printWidth: 120
useTabs: false
semi: true
arrowParens: "avoid"
tabWidth: 2
endOfLine: "lf"
singleQuote: true

View File

@ -9,6 +9,6 @@ const AdminView = () => {
);
};
AdminView.displayName = "AdminView";
AdminView.displayName = 'AdminView';
export default AdminView;

View File

@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
import CssBaseline from '@material-ui/core/CssBaseline';
import LinearProgress from '@material-ui/core/LinearProgress';
import { VotingContext } from '../context';
import { Route, Switch, withRouter } from "react-router-dom";
import { Route, Switch, withRouter } from 'react-router-dom';
import AdminView from '../components/AdminView';
import TitleScreen from './flow/TitleScreen';
import LearnAboutBallots from './flow/LearnAboutBallots';
@ -27,10 +27,8 @@ import PollSchedule from './flow/create/PollSchedule';
import PollReview from './flow/create/PollReview';
import PollCreationResults from './flow/create/PollCreationResults';
class Voting extends PureComponent {
state = {
state = {
addPoll: false,
pollTokenBalances: [],
votes: [],
@ -42,87 +40,322 @@ class Voting extends PureComponent {
};
updatePollBalance = (pollId, tokenBalance, ethBalance, votes) => {
const {pollTokenBalances} = this.state;
const { pollTokenBalances } = this.state;
pollTokenBalances[pollId] = { tokenBalance, ethBalance };
this.setState({pollTokenBalances, votes});
}
this.setState({ pollTokenBalances, votes });
};
setVotesToReview = votes => {
this.setState({ votes });
};
setVotesToReview = (votes) => {
this.setState({votes});
}
setTransactionHash = (idPoll, transactionHash) => {
const stHash = this.state.transactionHash;
stHash[idPoll] = transactionHash;
this.setState({transactionHash: stHash});
}
this.setState({ transactionHash: stHash });
};
setTransactionPromise = (idPoll, transaction) => {
const sTrx = this.state.transaction;
sTrx[idPoll] = transaction;
this.setState({transaction: sTrx});
}
this.setState({ transaction: sTrx });
};
setPollTransactionHash = (transactionHash) => {
this.setState({pollTransactionHash: transactionHash});
}
setPollTransactionHash = transactionHash => {
this.setState({ pollTransactionHash: transactionHash });
};
setPollTransactionPromise = (transaction) => {
this.setState({pollTransaction: transaction});
}
setPollTransactionPromise = transaction => {
this.setState({ pollTransaction: transaction });
};
assignToPoll = (newData) => {
assignToPoll = newData => {
const pollCr = Object.assign(this.state.pollCr, newData);
this.setState({pollCr});
}
this.setState({ pollCr });
};
resetPoll = ()=>{
this.setState({pollCr: {}});
}
resetPoll = () => {
this.setState({ pollCr: {} });
};
render(){
render() {
const { addPoll, pollTokenBalances, votes, transaction, transactionHash, start, end } = this.state;
return (
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<VotingContext.Consumer>
{({ getPolls, rawPolls, loading, symbol, decimals, name, replacePoll, loadPollContent, loadPollRange, loadMorePolls, start, resetPollCounter, end }) =>
<div>
<CssBaseline />
{loading && <LinearProgress />}
<div id="votingDapp">
<Switch>
<Route exact path="/" render={props => <LandingPage polls={rawPolls} replacePoll={replacePoll} resetPollCounter={resetPollCounter} symbol={symbol} decimals={decimals} />} />
<Route path="/titleScreen/:id" render={props => <TitleScreen polls={rawPolls} idPoll={props.match.params.id} loadPollContent={loadPollContent} symbol={symbol} decimals={decimals} />} />
<Route path="/otherPolls/:pollType?" render={props => <OtherPolls polls={rawPolls} pollType={props.match.params.pollType} loadPollContent={loadPollContent} loadMorePolls={loadMorePolls} start={start} end={end} addHandlerKey={true} loadPollRange={loadPollRange} symbol={symbol} decimals={decimals} />} />
<Route path="/learn/:id" render={props => <LearnAboutBallots polls={rawPolls} idPoll={props.match.params.id} loadPollContent={loadPollContent} symbol={symbol} decimals={decimals} />} />
<Route path="/votingHelp/:id" render={props => <HowVotingWorks idPoll={props.match.params.id} polls={rawPolls} updateBalances={this.updatePollBalance} loadPollContent={loadPollContent} symbol={symbol} decimals={decimals} name={name} />} />
<Route path="/votingCredits/:id" render={props => <VotingCredits polls={rawPolls} idPoll={props.match.params.id} balances={pollTokenBalances} loadPollContent={loadPollContent} symbol={symbol} decimals={decimals} />} />
<Route path="/wallet/:id" render={props => <ConnectYourWallet polls={rawPolls} idPoll={props.match.params.id} updateBalances={this.updatePollBalance} symbol={symbol} decimals={decimals} />} />
<Route path="/otherWallets/:id" render={props => <OtherWallets idPoll={props.match.params.id} polls={rawPolls} symbol={symbol} decimals={decimals} />} />
<Route path="/results/:id" render={props => <Results polls={rawPolls} idPoll={props.match.params.id} transaction={transaction} transactionHash={transactionHash} loadPollContent={loadPollContent} symbol={symbol} decimals={decimals} />} />
<Route path="/externalWallet/:id" render={props => <ExternalWallet polls={rawPolls} idPoll={props.match.params.id} updateBalances={this.updatePollBalance} symbol={symbol} decimals={decimals} />} />
<Route path="/voting/:id/:back?" render={props => <PollVoting polls={rawPolls} idPoll={props.match.params.id} balances={pollTokenBalances} originalVotes={votes} back={!!props.match.params.back} setVotesToReview={this.setVotesToReview} symbol={symbol} decimals={decimals} />} />
<Route path="/review/:id" render={props => <ReviewVotes polls={rawPolls} idPoll={props.match.params.id} votes={votes} balances={pollTokenBalances} setTransactionPromise={this.setTransactionPromise} setTransactionHash={this.setTransactionHash} symbol={symbol} decimals={decimals} />} />
<Route path="/admin" render={() => <AdminView symbol={symbol} decimals={decimals} />} />
<VotingContext.Consumer>
{({
getPolls,
rawPolls,
loading,
symbol,
decimals,
name,
replacePoll,
loadPollContent,
loadPollRange,
loadMorePolls,
start,
resetPollCounter,
end
}) => (
<div>
<CssBaseline />
{loading && <LinearProgress />}
<div id="votingDapp">
<Switch>
<Route
exact
path="/"
render={props => (
<LandingPage
polls={rawPolls}
replacePoll={replacePoll}
resetPollCounter={resetPollCounter}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/titleScreen/:id"
render={props => (
<TitleScreen
polls={rawPolls}
idPoll={props.match.params.id}
loadPollContent={loadPollContent}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/otherPolls/:pollType?"
render={props => (
<OtherPolls
polls={rawPolls}
pollType={props.match.params.pollType}
loadPollContent={loadPollContent}
loadMorePolls={loadMorePolls}
start={start}
end={end}
addHandlerKey={true}
loadPollRange={loadPollRange}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/learn/:id"
render={props => (
<LearnAboutBallots
polls={rawPolls}
idPoll={props.match.params.id}
loadPollContent={loadPollContent}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/votingHelp/:id"
render={props => (
<HowVotingWorks
idPoll={props.match.params.id}
polls={rawPolls}
updateBalances={this.updatePollBalance}
loadPollContent={loadPollContent}
symbol={symbol}
decimals={decimals}
name={name}
/>
)}
/>
<Route
path="/votingCredits/:id"
render={props => (
<VotingCredits
polls={rawPolls}
idPoll={props.match.params.id}
balances={pollTokenBalances}
loadPollContent={loadPollContent}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/wallet/:id"
render={props => (
<ConnectYourWallet
polls={rawPolls}
idPoll={props.match.params.id}
updateBalances={this.updatePollBalance}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/otherWallets/:id"
render={props => (
<OtherWallets
idPoll={props.match.params.id}
polls={rawPolls}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/results/:id"
render={props => (
<Results
polls={rawPolls}
idPoll={props.match.params.id}
transaction={transaction}
transactionHash={transactionHash}
loadPollContent={loadPollContent}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/externalWallet/:id"
render={props => (
<ExternalWallet
polls={rawPolls}
idPoll={props.match.params.id}
updateBalances={this.updatePollBalance}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/voting/:id/:back?"
render={props => (
<PollVoting
polls={rawPolls}
idPoll={props.match.params.id}
balances={pollTokenBalances}
originalVotes={votes}
back={!!props.match.params.back}
setVotesToReview={this.setVotesToReview}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/review/:id"
render={props => (
<ReviewVotes
polls={rawPolls}
idPoll={props.match.params.id}
votes={votes}
balances={pollTokenBalances}
setTransactionPromise={this.setTransactionPromise}
setTransactionHash={this.setTransactionHash}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route path="/admin" render={() => <AdminView symbol={symbol} decimals={decimals} />} />
<Route path="/poll/create" render={() => <PollCreationCredits poll={this.state.pollCr} resetPoll={this.resetPoll} symbol={symbol} decimals={decimals} />} />
<Route path="/poll/title" render={() => <PollTitle assignToPoll={this.assignToPoll} poll={this.state.pollCr} symbol={symbol} decimals={decimals} />} />
<Route path="/poll/description" render={() => <PollDescription assignToPoll={this.assignToPoll} poll={this.state.pollCr} symbol={symbol} decimals={decimals} />} />
<Route path="/poll/options" render={() => <PollOptions assignToPoll={this.assignToPoll} poll={this.state.pollCr} symbol={symbol} decimals={decimals} />} />
<Route path="/poll/schedule" render={() => <PollSchedule assignToPoll={this.assignToPoll} poll={this.state.pollCr} symbol={symbol} decimals={decimals} />} />
<Route path="/poll/review" render={() => <PollReview poll={this.state.pollCr} setPollTransactionHash={this.setPollTransactionHash} setPollTransactionPromise={this.setPollTransactionPromise} symbol={symbol} decimals={decimals} />} />
<Route path="/poll/results" render={() => <PollCreationResults loadPollContent={loadPollContent} getPolls={getPolls} resetPoll={this.resetPoll} poll={this.state.pollCr} pollTransactionHash={this.state.pollTransactionHash} pollTransaction={this.state.pollTransaction} symbol={symbol} decimals={decimals} />} />
</Switch>
<Route
path="/poll/create"
render={() => (
<PollCreationCredits
poll={this.state.pollCr}
resetPoll={this.resetPoll}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/poll/title"
render={() => (
<PollTitle
assignToPoll={this.assignToPoll}
poll={this.state.pollCr}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/poll/description"
render={() => (
<PollDescription
assignToPoll={this.assignToPoll}
poll={this.state.pollCr}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/poll/options"
render={() => (
<PollOptions
assignToPoll={this.assignToPoll}
poll={this.state.pollCr}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/poll/schedule"
render={() => (
<PollSchedule
assignToPoll={this.assignToPoll}
poll={this.state.pollCr}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/poll/review"
render={() => (
<PollReview
poll={this.state.pollCr}
setPollTransactionHash={this.setPollTransactionHash}
setPollTransactionPromise={this.setPollTransactionPromise}
symbol={symbol}
decimals={decimals}
/>
)}
/>
<Route
path="/poll/results"
render={() => (
<PollCreationResults
loadPollContent={loadPollContent}
getPolls={getPolls}
resetPoll={this.resetPoll}
poll={this.state.pollCr}
pollTransactionHash={this.state.pollTransactionHash}
pollTransaction={this.state.pollTransaction}
symbol={symbol}
decimals={decimals}
/>
)}
/>
</Switch>
</div>
</div>
</div>
}
</VotingContext.Consumer>
)}
</VotingContext.Consumer>
</MuiPickersUtilsProvider>
)
);
}
}

View File

@ -1,61 +1,94 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import ListItemText from '@material-ui/core/ListItemText';
import ListItem from '@material-ui/core/ListItem';
import List from '@material-ui/core/List';
import Divider from '@material-ui/core/Divider';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import IconButton from '@material-ui/core/IconButton';
import Typography from '@material-ui/core/Typography';
import CloseIcon from '@material-ui/icons/Close';
import Slide from '@material-ui/core/Slide';
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import ListItemText from '@material-ui/core/ListItemText';
import ListItem from '@material-ui/core/ListItem';
import List from '@material-ui/core/List';
import Divider from '@material-ui/core/Divider';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import IconButton from '@material-ui/core/IconButton';
import Typography from '@material-ui/core/Typography';
import CloseIcon from '@material-ui/icons/Close';
import Slide from '@material-ui/core/Slide';
function Transition(props) {
return <Slide direction="up" {...props} />;
}
return <Slide direction="up" {...props} />;
}
const HelpDialog = props => (
<Dialog
fullScreen
open={props.open}
onClose={props.handleClose}
TransitionComponent={Transition}
className="helpDialog"
>
<AppBar style={{ position: 'relative' }}>
<Toolbar>
<IconButton color="inherit" onClick={props.handleClose} aria-label="Close">
<CloseIcon />
</IconButton>
<Typography variant="display1" color="inherit">
Need help?
</Typography>
</Toolbar>
</AppBar>
<Typography variant="display1">Chat to us on Status</Typography>
<Typography variant="body1">
Tap here:{' '}
<a href="https://get.status.im/chat/public/status-snt-voting-dapp">
get.status.im/chat/public/status-snt-voting-dapp
</a>{' '}
to chat with us.{' '}
</Typography>
<Typography variant="display1">What are voting credits?</Typography>
<Typography variant="body1">
Voting Credits are used to purchase votes. The amount of voting credits equals the amount of {props.symbol} you
had in the wallet address used to open the Voting Dapp when the poll was created.
</Typography>
<Typography variant="display1">Why do I have zero voting credits?</Typography>
<Typography variant="body1">
You have zero voting credits because the wallet used to open the voting dapp had zero {props.symbol} in it at the
time the poll was created.
</Typography>
<Typography variant="display1">Why do votes cost more voting credits for every additional vote?</Typography>
<Typography variant="body1">
The price of votes equals its square in voting credits. This means purchasing the first vote on a ballot costs 1
voting credit, the second vote will require a total of 4 voting credits, the third vote will cost 9 voting credits
in total.
</Typography>
<Typography variant="body1">This is done to prevent domination of the vote by large token holders.</Typography>
<Typography variant="display1">How do I connect with a Tezors or Ledger?</Typography>
<Typography variant="body1">
You may connect with a Ledger or Tezors via the metamask intergration. Information for how to connect to Trezor
can be found here:{' '}
<a href="https://medium.com/metamask/trezor-integration-in-metamask-a8eaeae7f499" target="_blank">
https://medium.com/metamask/trezor-integration-in-metamask-a8eaeae7f499
</a>
</Typography>
<Typography variant="body1">
And Ledger here:{' '}
<a href="https://medium.com/metamask/metamask-now-supports-ledger-hardware-wallets-847f4d51546" target="_blank">
https://medium.com/metamask/metamask-now-supports-ledger-hardware-wallets-847f4d51546
</a>
</Typography>
<Typography variant="body1">
Please note that for ledger to work, the ethereum application on ledger needs to be 1.2.4 and above. Please find
instructions to do so here:{' '}
<a href="https://support.ledgerwallet.com/hc/en-us/articles/360009576554-Ethereum-ETH-" target="_blank">
https://support.ledgerwallet.com/hc/en-us/articles/360009576554-Ethereum-ETH-
</a>
</Typography>
<Typography variant="display1">Why do I need to pay ETH to vote?</Typography>
<Typography variant="body1">
Voting is done "on-chain", this means that your vote is written to the ethereum blockchain, ensuring that your
vote is transparent and immutable. To write any data to the ethereum blockchain requires miners to validate the
transaction. Miner's require an incentive in the form of ETH to mine a transaction.
</Typography>
</Dialog>
);
const HelpDialog = (props) => <Dialog
fullScreen
open={props.open}
onClose={props.handleClose}
TransitionComponent={Transition}
className="helpDialog"
>
<AppBar style={{position: "relative"}}>
<Toolbar>
<IconButton color="inherit" onClick={props.handleClose} aria-label="Close">
<CloseIcon />
</IconButton>
<Typography variant="display1" color="inherit">
Need help?
</Typography>
</Toolbar>
</AppBar>
<Typography variant="display1">Chat to us on Status</Typography>
<Typography variant="body1">Tap here: <a href="https://get.status.im/chat/public/status-snt-voting-dapp">get.status.im/chat/public/status-snt-voting-dapp</a> to chat with us. </Typography>
<Typography variant="display1">What are voting credits?</Typography>
<Typography variant="body1">Voting Credits are used to purchase votes. The amount of voting credits equals the amount of {props.symbol} you had in the wallet address used to open the Voting Dapp when the poll was created.</Typography>
<Typography variant="display1">Why do I have zero voting credits?</Typography>
<Typography variant="body1">You have zero voting credits because the wallet used to open the voting dapp had zero {props.symbol} in it at the time the poll was created.</Typography>
<Typography variant="display1">Why do votes cost more voting credits for every additional vote?</Typography>
<Typography variant="body1">The price of votes equals its square in voting credits. This means purchasing the first vote on a ballot costs 1 voting credit, the second vote will require a total of 4 voting credits, the third vote will cost 9 voting credits in total.</Typography>
<Typography variant="body1">This is done to prevent domination of the vote by large token holders.</Typography>
<Typography variant="display1">How do I connect with a Tezors or Ledger?</Typography>
<Typography variant="body1">You may connect with a Ledger or Tezors via the metamask intergration. Information for how to connect to Trezor can be found here: <a href="https://medium.com/metamask/trezor-integration-in-metamask-a8eaeae7f499" target="_blank">https://medium.com/metamask/trezor-integration-in-metamask-a8eaeae7f499</a></Typography>
<Typography variant="body1">And Ledger here: <a href="https://medium.com/metamask/metamask-now-supports-ledger-hardware-wallets-847f4d51546" target="_blank">https://medium.com/metamask/metamask-now-supports-ledger-hardware-wallets-847f4d51546</a></Typography>
<Typography variant="body1">Please note that for ledger to work, the ethereum application on ledger needs to be 1.2.4 and above. Please find instructions to do so here: <a href="https://support.ledgerwallet.com/hc/en-us/articles/360009576554-Ethereum-ETH-" target="_blank">https://support.ledgerwallet.com/hc/en-us/articles/360009576554-Ethereum-ETH-</a></Typography>
<Typography variant="display1">Why do I need to pay ETH to vote?</Typography>
<Typography variant="body1">Voting is done "on-chain", this means that your vote is written to the ethereum blockchain, ensuring that your vote is transparent and immutable. To write any data to the ethereum blockchain requires miners to validate the transaction. Miner's require an incentive in the form of ETH to mine a transaction.</Typography>
</Dialog>
export default HelpDialog;
export default HelpDialog;

View File

@ -1,12 +1,12 @@
import Button from '@material-ui/core/Button';
import React, {Component, Fragment} from 'react';
import React, { Component, Fragment } from 'react';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import CardActions from '@material-ui/core/CardActions';
import Typography from '@material-ui/core/Typography';
import { withRouter } from 'react-router-dom';
import DappToken from 'Embark/contracts/DappToken';
import PollManager from 'Embark/contracts/PollManager';
import DappToken from 'Embark/contracts/DappToken';
import PollManager from 'Embark/contracts/PollManager';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
@ -14,12 +14,10 @@ import DialogTitle from '@material-ui/core/DialogTitle';
import utils from '../../utils/utils';
class HowVotingWorks extends Component {
state = {
open: false,
tip: 0
}
};
handleClickOpen = tip => () => {
this.setState({
@ -32,163 +30,206 @@ class HowVotingWorks extends Component {
this.setState({ open: false });
};
componentDidUpdate(prevProps){
componentDidUpdate(prevProps) {
if (this.props.polls !== prevProps.polls && this.props.polls) {
// TODO: see how to extract this. Maybe a higher order component?
const poll = this.props.polls.find(p => p.idPoll == this.props.idPoll);
if(poll && !poll.content){
if (poll && !poll.content) {
this.props.loadPollContent(poll);
}
}
}
checkWeb3 = async () => {
if(!window.web3){
this.props.history.push("/wallet/" + this.props.idPoll);
if (!window.web3) {
this.props.history.push('/wallet/' + this.props.idPoll);
return;
}
const {history, polls, updateBalances, idPoll, decimals} = this.props;
if(!polls) return;
const { history, polls, updateBalances, idPoll, decimals } = this.props;
if (!polls) return;
let cont = true;
if (window.ethereum) {
try {
await ethereum.enable();
web3.setProvider(ethereum);
const accounts = await web3.eth.getAccounts();
web3.eth.defaultAccount = accounts[0];
await ethereum.enable();
web3.setProvider(ethereum);
const accounts = await web3.eth.getAccounts();
web3.eth.defaultAccount = accounts[0];
} catch (error) {
cont = false;
}
}
if(cont){
if (cont) {
// TODO: extract this code to utils. It's repeated in ConnectYourWallt, ExternalWallet and HowVotingWorks
const poll = polls.find(p => p.idPoll == idPoll);
if(!poll) return null;
const tknVotes = await PollManager.methods.getVote(idPoll, web3.eth.defaultAccount).call({from: web3.eth.defaultAccount});
const votes = tknVotes.map(x => Math.sqrt(parseInt( utils.fromTokenDecimals(x, decimals))));
const tokenBalance = await DappToken.methods.balanceOfAt(web3.eth.defaultAccount, poll._startBlock).call({from: web3.eth.defaultAccount});
if (!poll) return null;
const tknVotes = await PollManager.methods
.getVote(idPoll, web3.eth.defaultAccount)
.call({ from: web3.eth.defaultAccount });
const votes = tknVotes.map(x => Math.sqrt(parseInt(utils.fromTokenDecimals(x, decimals))));
const tokenBalance = await DappToken.methods
.balanceOfAt(web3.eth.defaultAccount, poll._startBlock)
.call({ from: web3.eth.defaultAccount });
const ethBalance = await web3.eth.getBalance(web3.eth.defaultAccount);
updateBalances(idPoll, tokenBalance, ethBalance, votes);
history.push('/votingCredits/' + idPoll);
}
}
};
render() {
const props = this.props;
return <Fragment><div className="section">
<Typography variant="headline">How voting works</Typography>
<InfoDialog
text={<Fragment>
{ this.state.tip == 1 && <p>When a vote is created, the Voting Dapp uses smart contracts to take a snapshot of all the wallets addresses which hold {this.props.symbol} and their {this.props.symbol} balances. These {this.props.symbol} balances are used to inform the number of voting credits a wallet address has. No {this.props.symbol} needs to be staked or committed, all that is required is to connect to the voting app with a wallet that had {this.props.symbol} in it before the "snapshot" was taken.</p> }
{ this.state.tip == 2 && <p>Voting credits are a representation of the {this.props.symbol} in the wallet address at the time the vote was created. When voting, the first ballot which is voted on will cost 1 voting credit. The second vote will cost an additional 3 voting credits to total 4 voting credits. Every additional vote will require its square in voting credits.</p> }
{ this.state.tip == 3 && <p>This voting process is called quadratic voting. It minimizes the effect that large token holders can have on the vote, measures the intensity of opinion and encourages a spread of voting amongst the ballots.</p> }
</Fragment>}
title={
<Fragment>
{ this.state.tip == 1 && <span>Any wallet with {this.props.symbol} can vote</span> }
{ this.state.tip == 2 && <span>You don't spend your {this.props.symbol}!</span> }
{ this.state.tip == 3 && <span>Your vote counts</span> }
</Fragment>
}
open={this.state.open}
onClose={this.handleClose}
/>
<Card className="card">
<CardContent>
<div className="left">
<span><img src="images/wallet.svg" width="23" /></span>
</div>
<div className="right">
<Typography gutterBottom component="h2">
Any wallet with {this.props.symbol} can vote
</Typography>
<Typography component="p">
When a poll is created a snapshot is taken of every wallet that holds {this.props.name} ({this.props.symbol}).
</Typography>
</div>
</CardContent>
<CardActions className="actionArea" style={{clear: "both", paddingLeft: "70px"}}>
<Button size="small" color="primary" onClick={this.handleClickOpen(1)}>Learn more</Button>
</CardActions>
</Card>
<Card className="card">
<CardContent>
<div className="left">
<span><img src="images/happy-face.svg" width="23" /></span>
</div>
<div className="right">
<Typography gutterBottom component="h2">
You don't spend your {this.props.symbol}!
</Typography>
<Typography component="p">
Your wallet gets one voting credit for every {this.props.symbol} it holds. To cast your vote, you sign a transaction, but you only spend a small amount of ETH for the transaction fee.
</Typography>
</div>
</CardContent>
<CardActions className="actionArea" style={{clear: "both", paddingLeft: "70px"}}>
<Button size="small" color="primary" onClick={this.handleClickOpen(2)}>Learn more</Button>
</CardActions>
</Card>
<Card className="card">
<CardContent>
<div className="left">
<span><img src="images/envelope.svg" width="23" /></span>
</div>
<div className="right">
<Typography gutterBottom component="h2">
Your vote counts
</Typography>
<Typography component="p">
Most votes when poll ends wins! Multiple votes cost more to prevent whales from controlling the vote
</Typography>
</div>
</CardContent>
<CardActions className="actionArea" style={{clear: "both", paddingLeft: "70px"}}>
<Button size="small" color="primary" onClick={this.handleClickOpen(3)}>Learn more</Button>
</CardActions>
</Card>
</div>
<div className="buttonNav">
<Button onClick={this.checkWeb3}>Next</Button>
</div>
</Fragment>;
}
}
class InfoDialog extends Component {
handleClose = () => {
this.props.onClose(this.props.selectedValue);
};
handleListItemClick = value => {
this.props.onClose(value);
};
render() {
const { onClose, text, title, ...other } = this.props;
return (
<Dialog onClose={this.handleClose} aria-labelledby="simple-dialog-title" {...other}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<Typography variant="body1" component="div">{text}</Typography>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color="primary" autoFocus>Ok</Button>
</DialogActions>
</Dialog>
<Fragment>
<div className="section">
<Typography variant="headline">How voting works</Typography>
<InfoDialog
text={
<Fragment>
{this.state.tip == 1 && (
<p>
When a vote is created, the Voting Dapp uses smart contracts to take a snapshot of all the wallets
addresses which hold {this.props.symbol} and their {this.props.symbol} balances. These{' '}
{this.props.symbol} balances are used to inform the number of voting credits a wallet address has.
No {this.props.symbol} needs to be staked or committed, all that is required is to connect to the
voting app with a wallet that had {this.props.symbol} in it before the "snapshot" was taken.
</p>
)}
{this.state.tip == 2 && (
<p>
Voting credits are a representation of the {this.props.symbol} in the wallet address at the time the
vote was created. When voting, the first ballot which is voted on will cost 1 voting credit. The
second vote will cost an additional 3 voting credits to total 4 voting credits. Every additional
vote will require its square in voting credits.
</p>
)}
{this.state.tip == 3 && (
<p>
This voting process is called quadratic voting. It minimizes the effect that large token holders can
have on the vote, measures the intensity of opinion and encourages a spread of voting amongst the
ballots.
</p>
)}
</Fragment>
}
title={
<Fragment>
{this.state.tip == 1 && <span>Any wallet with {this.props.symbol} can vote</span>}
{this.state.tip == 2 && <span>You don't spend your {this.props.symbol}!</span>}
{this.state.tip == 3 && <span>Your vote counts</span>}
</Fragment>
}
open={this.state.open}
onClose={this.handleClose}
/>
<Card className="card">
<CardContent>
<div className="left">
<span>
<img src="images/wallet.svg" width="23" />
</span>
</div>
<div className="right">
<Typography gutterBottom component="h2">
Any wallet with {this.props.symbol} can vote
</Typography>
<Typography component="p">
When a poll is created a snapshot is taken of every wallet that holds {this.props.name} (
{this.props.symbol}).
</Typography>
</div>
</CardContent>
<CardActions className="actionArea" style={{ clear: 'both', paddingLeft: '70px' }}>
<Button size="small" color="primary" onClick={this.handleClickOpen(1)}>
Learn more
</Button>
</CardActions>
</Card>
<Card className="card">
<CardContent>
<div className="left">
<span>
<img src="images/happy-face.svg" width="23" />
</span>
</div>
<div className="right">
<Typography gutterBottom component="h2">
You don't spend your {this.props.symbol}!
</Typography>
<Typography component="p">
Your wallet gets one voting credit for every {this.props.symbol} it holds. To cast your vote, you sign
a transaction, but you only spend a small amount of ETH for the transaction fee.
</Typography>
</div>
</CardContent>
<CardActions className="actionArea" style={{ clear: 'both', paddingLeft: '70px' }}>
<Button size="small" color="primary" onClick={this.handleClickOpen(2)}>
Learn more
</Button>
</CardActions>
</Card>
<Card className="card">
<CardContent>
<div className="left">
<span>
<img src="images/envelope.svg" width="23" />
</span>
</div>
<div className="right">
<Typography gutterBottom component="h2">
Your vote counts
</Typography>
<Typography component="p">
Most votes when poll ends wins! Multiple votes cost more to prevent whales from controlling the vote
</Typography>
</div>
</CardContent>
<CardActions className="actionArea" style={{ clear: 'both', paddingLeft: '70px' }}>
<Button size="small" color="primary" onClick={this.handleClickOpen(3)}>
Learn more
</Button>
</CardActions>
</Card>
</div>
<div className="buttonNav">
<Button onClick={this.checkWeb3}>Next</Button>
</div>
</Fragment>
);
}
}
export default withRouter(HowVotingWorks);
class InfoDialog extends Component {
handleClose = () => {
this.props.onClose(this.props.selectedValue);
};
handleListItemClick = value => {
this.props.onClose(value);
};
render() {
const { onClose, text, title, ...other } = this.props;
return (
<Dialog onClose={this.handleClose} aria-labelledby="simple-dialog-title" {...other}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<Typography variant="body1" component="div">
{text}
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color="primary" autoFocus>
Ok
</Button>
</DialogActions>
</Dialog>
);
}
}
export default withRouter(HowVotingWorks);

View File

@ -1,180 +1,198 @@
import Button from '@material-ui/core/Button';
import React, {Component, Fragment} from 'react';
import React, { Component, Fragment } from 'react';
import Typography from '@material-ui/core/Typography';
import {Link} from "react-router-dom";
import { Link } from 'react-router-dom';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import { withRouter } from 'react-router-dom'
import { withRouter } from 'react-router-dom';
import utils from '../../utils/utils';
// TODO: extract to utils
function pad(number, length) {
var str = '' + number;
while (str.length < length) {
str = '0' + str;
}
return str;
var str = String(number);
while (str.length < length) {
str = '0' + str;
}
Date.prototype.DDMMYYYY = function () {
var yyyy = this.getFullYear().toString();
var MM = pad(this.getMonth() + 1,2);
var dd = pad(this.getDate(), 2);
return dd + '/' + MM + '/' + yyyy ;
};
class LandingPage extends Component {
state = {
openPoll: null,
closedPoll: null
}
componentDidMount(){
this.loadLatestPolls();
}
gotoOtherPolls = (type) => () => {
this.props.resetPollCounter();
this.props.history.push('/otherPolls/' + type);
}
createPoll = async () => {
if(!window.web3){
this.props.history.push("/wallet/poll-creation");
return;
}
let cont = true;
if (window.ethereum) {
try {
await ethereum.enable();
web3.setProvider(ethereum);
const accounts = await web3.eth.getAccounts();
web3.eth.defaultAccount = accounts[0];
} catch (error) {
cont = false;
}
}
if(cont)
this.props.history.push('/poll/create');
}
loadLatestPolls = () => {
let polls = this.props.polls;
if(polls && polls.length){
const openPoll = polls.find(x => !x._canceled && x._endTime > (new Date()).getTime() / 1000);
if(openPoll)
EmbarkJS.Storage.get(web3.utils.toAscii(openPoll._description)).then(content => {
openPoll.content = JSON.parse(content);
this.setState({openPoll})
this.props.replacePoll(openPoll);
})
const closedPoll = polls.find(x => !x._canceled && x._endTime < (new Date()).getTime() / 1000);
if(closedPoll)
EmbarkJS.Storage.get(web3.utils.toAscii(closedPoll._description)).then(content => {
closedPoll.content = JSON.parse(content);
this.setState({closedPoll});
this.props.replacePoll(closedPoll);
})
}
}
componentDidUpdate(prevProps) {
if (this.props.polls !== prevProps.polls) {
this.loadLatestPolls();
}
}
render(){
const { openPoll, closedPoll } = this.state;
const { decimals } = this.props;
if(openPoll){
openPoll._tokenSum = 0;
openPoll._votesSum = 0;
for(let i = 0; i < openPoll._numBallots; i++){
openPoll._tokenSum += parseInt(utils.fromTokenDecimals(openPoll._tokenTotal[i], decimals), 10);
openPoll._votesSum += parseInt(openPoll._quadraticVotes[i], 10);
}
}
if(closedPoll){
closedPoll._tokenSum = 0;
closedPoll._votesSum = 0;
for(let i = 0; i < closedPoll._numBallots; i++){
closedPoll._tokenSum += parseInt(utils.fromTokenDecimals(closedPoll._tokenTotal[i], decimals), 10);
closedPoll._votesSum += parseInt(closedPoll._quadraticVotes[i], 10);
}
}
return <Fragment>
<div>
<div className="section" style={{marginBottom: 0}}>
<img src="images/status-logo.svg" width="36" />
<Button className="createPollBtn" onClick={this.createPoll}><img src="images/create-poll.svg" width="23" /></Button>
<Typography variant="headline">Status {this.props.symbol} Voting</Typography>
<Typography variant="body1" component="div" style={{marginTop: '24px', fontSize: '15px', lineHeight: '22px'}}>Create a poll or vote in one. Your vote helps us decide our product and community direction.</Typography>
</div>
{ openPoll && openPoll.content &&
<div className="section" style={{paddingTop: 0, marginBottom: "26px"}}>
<h2 className="pollTypeTitle">Open Polls</h2>
<Card className="card poll">
<CardContent>
<Typography gutterBottom component="h2">{openPoll.content.title}</Typography>
<span className="pollClosingDate">Closes: {new Date(openPoll._endTime * 1000).DDMMYYYY()} </span>
<p className="stats">
Voters: {openPoll._voters}<br />
Total votes: {openPoll._votesSum}<br />
Total {this.props.symbol}: {openPoll._tokenSum}
</p>
<Link to={"/titleScreen/" + openPoll.idPoll} className="arrowRightLink">VOTE NOW</Link>
</CardContent>
</Card>
<div style={{textAlign: "center", marginTop: "35px"}}>
<a className="landingPageButton" onClick={this.gotoOtherPolls('open')}>More open polls</a>
</div>
</div>
}
{ closedPoll && closedPoll.content &&
<div className="section" style={{paddingTop: 0}}>
<h2 className="pollTypeTitle">Closed Polls</h2>
<Card className="card poll">
<CardContent>
<Typography gutterBottom component="h2">{closedPoll.content.title}</Typography>
<span className="pollClosingDate">Closed: {new Date(closedPoll._endTime * 1000).DDMMYYYY()} </span>
<p className="stats">
Voters: {closedPoll._voters}<br />
Total votes: {closedPoll._votesSum}<br />
Total {this.props.symbol}: {closedPoll._tokenSum}<br />
</p>
<Link to={"/results/" + closedPoll.idPoll} className="arrowRightLink">See results</Link>
</CardContent>
</Card>
<div style={{textAlign: "center", marginTop: "35px"}}>
<a className="landingPageButton" onClick={this.gotoOtherPolls('closed')}>More closed polls</a>
</div>
</div>
}
</div>
</Fragment>;
}
return str;
}
export default withRouter(LandingPage);
Date.prototype.DDMMYYYY = function() {
var yyyy = this.getFullYear().toString();
var MM = pad(this.getMonth() + 1, 2);
var dd = pad(this.getDate(), 2);
return dd + '/' + MM + '/' + yyyy;
};
class LandingPage extends Component {
state = {
openPoll: null,
closedPoll: null
};
componentDidMount() {
this.loadLatestPolls();
}
gotoOtherPolls = type => () => {
this.props.resetPollCounter();
this.props.history.push('/otherPolls/' + type);
};
createPoll = async () => {
if (!window.web3) {
this.props.history.push('/wallet/poll-creation');
return;
}
let cont = true;
if (window.ethereum) {
try {
await ethereum.enable();
web3.setProvider(ethereum);
const accounts = await web3.eth.getAccounts();
web3.eth.defaultAccount = accounts[0];
} catch (error) {
cont = false;
}
}
if (cont) this.props.history.push('/poll/create');
};
loadLatestPolls = () => {
let polls = this.props.polls;
if (polls && polls.length) {
const openPoll = polls.find(x => !x._canceled && x._endTime > new Date().getTime() / 1000);
if (openPoll)
EmbarkJS.Storage.get(web3.utils.toAscii(openPoll._description)).then(content => {
openPoll.content = JSON.parse(content);
this.setState({ openPoll });
this.props.replacePoll(openPoll);
});
const closedPoll = polls.find(x => !x._canceled && x._endTime < new Date().getTime() / 1000);
if (closedPoll)
EmbarkJS.Storage.get(web3.utils.toAscii(closedPoll._description)).then(content => {
closedPoll.content = JSON.parse(content);
this.setState({ closedPoll });
this.props.replacePoll(closedPoll);
});
}
};
componentDidUpdate(prevProps) {
if (this.props.polls !== prevProps.polls) {
this.loadLatestPolls();
}
}
render() {
const { openPoll, closedPoll } = this.state;
const { decimals } = this.props;
if (openPoll) {
openPoll._tokenSum = 0;
openPoll._votesSum = 0;
for (let i = 0; i < openPoll._numBallots; i++) {
openPoll._tokenSum += parseInt(utils.fromTokenDecimals(openPoll._tokenTotal[i], decimals), 10);
openPoll._votesSum += parseInt(openPoll._quadraticVotes[i], 10);
}
}
if (closedPoll) {
closedPoll._tokenSum = 0;
closedPoll._votesSum = 0;
for (let i = 0; i < closedPoll._numBallots; i++) {
closedPoll._tokenSum += parseInt(utils.fromTokenDecimals(closedPoll._tokenTotal[i], decimals), 10);
closedPoll._votesSum += parseInt(closedPoll._quadraticVotes[i], 10);
}
}
return (
<Fragment>
<div>
<div className="section" style={{ marginBottom: 0 }}>
<img src="images/status-logo.svg" width="36" />
<Button className="createPollBtn" onClick={this.createPoll}>
<img src="images/create-poll.svg" width="23" />
</Button>
<Typography variant="headline">Status {this.props.symbol} Voting</Typography>
<Typography
variant="body1"
component="div"
style={{ marginTop: '24px', fontSize: '15px', lineHeight: '22px' }}
>
Create a poll or vote in one. Your vote helps us decide our product and community direction.
</Typography>
</div>
{openPoll && openPoll.content && (
<div className="section" style={{ paddingTop: 0, marginBottom: '26px' }}>
<h2 className="pollTypeTitle">Open Polls</h2>
<Card className="card poll">
<CardContent>
<Typography gutterBottom component="h2">
{openPoll.content.title}
</Typography>
<span className="pollClosingDate">Closes: {new Date(openPoll._endTime * 1000).DDMMYYYY()} </span>
<p className="stats">
Voters: {openPoll._voters}
<br />
Total votes: {openPoll._votesSum}
<br />
Total {this.props.symbol}: {openPoll._tokenSum}
</p>
<Link to={'/titleScreen/' + openPoll.idPoll} className="arrowRightLink">
VOTE NOW
</Link>
</CardContent>
</Card>
<div style={{ textAlign: 'center', marginTop: '35px' }}>
<a className="landingPageButton" onClick={this.gotoOtherPolls('open')}>
More open polls
</a>
</div>
</div>
)}
{closedPoll && closedPoll.content && (
<div className="section" style={{ paddingTop: 0 }}>
<h2 className="pollTypeTitle">Closed Polls</h2>
<Card className="card poll">
<CardContent>
<Typography gutterBottom component="h2">
{closedPoll.content.title}
</Typography>
<span className="pollClosingDate">Closed: {new Date(closedPoll._endTime * 1000).DDMMYYYY()} </span>
<p className="stats">
Voters: {closedPoll._voters}
<br />
Total votes: {closedPoll._votesSum}
<br />
Total {this.props.symbol}: {closedPoll._tokenSum}
<br />
</p>
<Link to={'/results/' + closedPoll.idPoll} className="arrowRightLink">
See results
</Link>
</CardContent>
</Card>
<div style={{ textAlign: 'center', marginTop: '35px' }}>
<a className="landingPageButton" onClick={this.gotoOtherPolls('closed')}>
More closed polls
</a>
</div>
</div>
)}
</div>
</Fragment>
);
}
}
export default withRouter(LandingPage);

View File

@ -1,6 +1,6 @@
import {Link} from "react-router-dom";
import { Link } from 'react-router-dom';
import Button from '@material-ui/core/Button';
import React, {Component, Fragment} from 'react';
import React, { Component, Fragment } from 'react';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
@ -29,82 +29,91 @@ class LearnAboutBallots extends Component {
this.setState({ open: false });
};
componentDidUpdate(prevProps){
componentDidUpdate(prevProps) {
if (this.props.polls !== prevProps.polls && this.props.polls) {
// TODO: see how to extract this. Maybe a higher order component?
const poll = this.props.polls.find(p => p.idPoll == this.props.idPoll);
if(poll && !poll.content){
if (poll && !poll.content) {
this.props.loadPollContent(poll);
}
}
}
render(){
const {polls, idPoll} = this.props;
render() {
const { polls, idPoll } = this.props;
if (!polls) return null;
if(!polls) return null;
const poll = polls.find(p => p.idPoll == idPoll);
if(!poll || !poll.content) return null;
if (!poll || !poll.content) return null;
const title = poll.content.title;
const ballots = poll.content.ballots;
return (<Fragment>
<div className="section">
<Typography variant="headline">{title}</Typography>
<BallotDialog
title={this.state.dialogTitle}
text={this.state.dialogText}
open={this.state.open}
onClose={this.handleClose}
/>
{
ballots.map((item, i) => {
return <Card key={i} className="card">
<CardContent className="ballotData">
<Typography gutterBottom component="h2">{item.title}</Typography>
return (
<Fragment>
<div className="section">
<Typography variant="headline">{title}</Typography>
<BallotDialog
title={this.state.dialogTitle}
text={this.state.dialogText}
open={this.state.open}
onClose={this.handleClose}
/>
{ballots.map((item, i) => {
return (
<Card key={i} className="card">
<CardContent className="ballotData">
<Typography gutterBottom component="h2">
{item.title}
</Typography>
<Typography component="p">{item.subtitle}</Typography>
</CardContent>
<CardActions className="actionArea">
<Button size="small" color="primary" onClick={() => this.handleClickOpen(item.title, item.content)}>Learn more</Button>
</CardActions>
</Card>
})
}
</div>
<div className="buttonNav">
<Link to={"/votingHelp/" + idPoll}><Button className="nextAction">Next</Button></Link>
</div>
</Fragment>
</CardContent>
<CardActions className="actionArea">
<Button size="small" color="primary" onClick={() => this.handleClickOpen(item.title, item.content)}>
Learn more
</Button>
</CardActions>
</Card>
);
})}
</div>
<div className="buttonNav">
<Link to={'/votingHelp/' + idPoll}>
<Button className="nextAction">Next</Button>
</Link>
</div>
</Fragment>
);
}
}
class BallotDialog extends Component {
handleClose = () => {
this.props.onClose(this.props.selectedValue);
};
handleListItemClick = value => {
this.props.onClose(value);
};
render() {
const { onClose, title, text, polls, ...other } = this.props;
return (
<Dialog onClose={this.handleClose} aria-labelledby="simple-dialog-title" {...other}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<Typography variant="body1" component="div">{text}</Typography>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color="primary" autoFocus>Ok</Button>
</DialogActions>
</Dialog>
<Dialog onClose={this.handleClose} aria-labelledby="simple-dialog-title" {...other}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<Typography variant="body1" component="div">
{text}
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color="primary" autoFocus>
Ok
</Button>
</DialogActions>
</Dialog>
);
}
}

View File

@ -1,133 +1,148 @@
import Button from '@material-ui/core/Button';
import React, {Component, Fragment} from 'react';
import React, { Component, Fragment } from 'react';
import Typography from '@material-ui/core/Typography';
import {Link} from "react-router-dom";
import { Link } from 'react-router-dom';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import utils from '../../utils/utils';
const pollsPerLoad = 3;
function pad(number, length) {
var str = '' + number;
while (str.length < length) {
str = '0' + str;
}
return str;
var str = String(number);
while (str.length < length) {
str = '0' + str;
}
return str;
}
// TODO: extract to utils
Date.prototype.DDMMYYYY = function () {
var yyyy = this.getFullYear().toString();
var MM = pad(this.getMonth() + 1,2);
var dd = pad(this.getDate(), 2);
return dd + '/' + MM + '/' + yyyy ;
};
Date.prototype.DDMMYYYY = function() {
var yyyy = this.getFullYear().toString();
var MM = pad(this.getMonth() + 1, 2);
var dd = pad(this.getDate(), 2);
return dd + '/' + MM + '/' + yyyy;
};
class OtherPolls extends Component {
state = {
firstLoad: true
};
state = {
firstLoad: true
loadIPFSpollContent = async () => {
let filterFn;
if (this.props.pollType == 'open') {
filterFn = x => !x._canceled && x._endTime > new Date().getTime() / 1000;
} else {
filterFn = x => !x._canceled && x._endTime < new Date().getTime() / 1000;
}
this.props.loadPollRange(filterFn, this.props.start, this.props.end);
};
loadMorePollsHandle = async () => {
let filterFn;
if (this.props.pollType == 'open') {
filterFn = x => !x._canceled && x._endTime > new Date().getTime() / 1000;
} else {
filterFn = x => !x._canceled && x._endTime < new Date().getTime() / 1000;
}
loadIPFSpollContent = async () => {
let filterFn;
if(this.props.pollType == 'open'){
filterFn = x => !x._canceled && x._endTime > (new Date()).getTime() / 1000;
} else {
filterFn = x => !x._canceled && x._endTime < (new Date()).getTime() / 1000;
}
this.props.loadPollRange(filterFn, this.props.start, this.props.end);
}
loadMorePollsHandle = async () => {
let filterFn;
if(this.props.pollType == 'open'){
filterFn = x => !x._canceled && x._endTime > (new Date()).getTime() / 1000;
} else {
filterFn = x => !x._canceled && x._endTime < (new Date()).getTime() / 1000;
}
this.props.loadMorePolls(filterFn);
};
this.props.loadMorePolls(filterFn);
componentWillMount() {
if (this.state.firstLoad) {
this.setState({ firstLoad: false });
this.loadIPFSpollContent();
}
}
render() {
let { pollType, polls } = this.props;
if (!polls) {
return null;
}
if (!pollType) pollType = 'open';
componentWillMount(){
if(this.state.firstLoad){
this.setState({firstLoad: false});
this.loadIPFSpollContent();
}
if (polls && polls.length) {
if (pollType == 'open') {
polls = polls.filter(x => !x._canceled && x._endTime > new Date().getTime() / 1000);
} else {
polls = polls.filter(x => !x._canceled && x._endTime < new Date().getTime() / 1000);
}
}
render() {
let {pollType, polls} = this.props;
if(!polls){
return null;
}
return (
<Fragment>
<div className="section" style={{ marginBottom: 0 }}>
<Typography variant="headline" className="otherPollsTitle">
{pollType == 'open' ? 'Open polls' : 'Closed polls'} <small>({polls.length})</small>
</Typography>
{polls.map((p, i) => {
if (i >= this.props.end) return null;
if (p._canceled) return null;
if(!pollType) pollType = 'open';
if(polls && polls.length){
if(pollType == 'open'){
polls = polls.filter(x => !x._canceled && x._endTime > (new Date()).getTime() / 1000);
} else {
polls = polls.filter(x => !x._canceled && x._endTime < (new Date()).getTime() / 1000);
p._tokenSum = 0;
p._votesSum = 0;
for (let i = 0; i < p._numBallots; i++) {
p._tokenSum += parseInt(utils.fromTokenDecimals(p._tokenTotal[i], this.props.decimals), 10);
p._votesSum += parseInt(p._quadraticVotes[i], 10);
}
}
if (p.content) {
return (
<Card className="card poll" key={i}>
<CardContent>
<Typography gutterBottom component="h2">
{p.content.title}
</Typography>
<Typography component="p" dangerouslySetInnerHTML={{ __html: p.content.description }} />
<span className="pollClosingDate">
{pollType == 'open' ? 'Closes: ' : 'Closed: '} {new Date(p._endTime * 1000).DDMMYYYY()}
</span>
<p className="stats">
Voters: {p._voters}
<br />
Total votes: {p._votesSum}
<br />
Total {this.props.symbol}: {p._tokenSum}
</p>
{pollType == 'open' && (
<Link to={'/titleScreen/' + p.idPoll} className="arrowRightLink">
VOTE NOW
</Link>
)}
{pollType != 'open' && (
<Link to={'/results/' + p.idPoll} className="arrowRightLink">
See results
</Link>
)}
</CardContent>
</Card>
);
}
return (
<Card className="card" key={i}>
<CardContent>
<img src="images/loadIndicator.gif" />
</CardContent>
</Card>
);
})}
return <Fragment>
<div className="section" style={{marginBottom: 0}}>
<Typography variant="headline" className="otherPollsTitle">{pollType == 'open' ? 'Open polls' : 'Closed polls'} <small>({polls.length})</small></Typography>
{
polls.map((p, i) => {
if(i >= this.props.end) return null;
if(p._canceled) return null;
p._tokenSum = 0;
p._votesSum = 0;
for(let i = 0; i < p._numBallots; i++){
p._tokenSum += parseInt(utils.fromTokenDecimals(p._tokenTotal[i], this.props.decimals), 10);
p._votesSum += parseInt(p._quadraticVotes[i], 10);
}
if(p.content){
return <Card className="card poll" key={i}>
<CardContent>
<Typography gutterBottom component="h2">{p.content.title}</Typography>
<Typography component="p" dangerouslySetInnerHTML={{__html: p.content.description}}></Typography>
<span className="pollClosingDate">{pollType == "open" ? "Closes: " : "Closed: "} {new Date(p._endTime * 1000).DDMMYYYY()}</span>
<p className="stats">
Voters: {p._voters}<br />
Total votes: {p._votesSum}<br />
Total {this.props.symbol}: {p._tokenSum}</p>
{ pollType == 'open' && <Link to={"/titleScreen/" + p.idPoll} className="arrowRightLink">VOTE NOW</Link> }
{ pollType != 'open' && <Link to={"/results/" + p.idPoll} className="arrowRightLink">See results</Link> }
</CardContent>
</Card>
} else {
return <Card className="card" key={i}>
<CardContent>
<img src="images/loadIndicator.gif" />
</CardContent>
</Card>
}
})
}
<div style={{textAlign:"center", marginTop: "40px"}}>
{ polls && polls.length > this.props.end &&
<a onClick={this.loadMorePollsHandle} className="landingPageButton">Show more polls</a> }
</div>
</div>
</Fragment>;
}
<div style={{ textAlign: 'center', marginTop: '40px' }}>
{polls && polls.length > this.props.end && (
<a onClick={this.loadMorePollsHandle} className="landingPageButton">
Show more polls
</a>
)}
</div>
</div>
</Fragment>
);
}
}
export default OtherPolls;

View File

@ -1,75 +1,72 @@
import Button from '@material-ui/core/Button';
import React, {Component, Fragment} from 'react';
import Typography from '@material-ui/core/Typography'
import React, { Component, Fragment } from 'react';
import Typography from '@material-ui/core/Typography';
import { withStyles } from '@material-ui/core/styles';
import Slider from '@material-ui/lab/Slider';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import { withRouter } from 'react-router-dom'
import { withRouter } from 'react-router-dom';
import utils from '../../utils/utils';
const styles = {
card: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
alignItems: 'center'
},
thumb: {
width: '24px',
height: '24px'
},
appBar: {
position: 'relative',
position: 'relative'
},
flex: {
flex: 1,
},
flex: 1
}
};
const arraysEqual = (arr1, arr2) => {
if(arr1.length !== arr2.length)
return false;
for(var i = arr1.length; i--;) {
if(arr1[i] !== arr2[i])
return false;
if (arr1.length !== arr2.length) return false;
for (var i = arr1.length; i--; ) {
if (arr1[i] !== arr2[i]) return false;
}
return true;
}
};
const shuffleArray = (array) => {
const shuffleArray = array => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]]; // eslint-disable-line no-param-reassign
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]]; // eslint-disable-line no-param-reassign
}
}
};
class PollVoting extends Component {
state = {
votes: [],
originalVotes: [],
voteOrder: [],
t: new Date()
}
};
componentDidMount(){
const {polls, originalVotes, idPoll, history} = this.props;
componentDidMount() {
const { polls, originalVotes, idPoll, history } = this.props;
if(!polls){
if (!polls) {
history.push('/');
return;
}
const poll = polls.find(p => p.idPoll == idPoll);
if(!poll){
if (!poll) {
history.push('/');
return;
}
const votes = [];
const voteOrder = [];
if(originalVotes.length){
for(let i = 0; i < poll._numBallots; i++){
if (originalVotes.length) {
for (let i = 0; i < poll._numBallots; i++) {
votes[i] = originalVotes[i];
voteOrder.push(i);
}
@ -87,89 +84,105 @@ class PollVoting extends Component {
updateVotes = i => numVotes => {
const votes = this.state.votes;
votes[i] = numVotes;
this.setState({votes, t: new Date()});
}
this.setState({ votes, t: new Date() });
};
sendToReview = () => {
const seconds = this.props.polls.find(p => p.idPoll == this.props.idPoll)._endTime - (new Date()).getTime() / 1000;
if(seconds <= 0){
alert("Poll is expired");
const seconds = this.props.polls.find(p => p.idPoll == this.props.idPoll)._endTime - new Date().getTime() / 1000;
if (seconds <= 0) {
alert('Poll is expired');
this.props.history.push('/');
} else {
this.props.setVotesToReview(this.state.votes);
this.props.history.push('/review/' + this.props.idPoll);
}
}
};
render(){
const {polls, classes, balances, idPoll, back, decimals} = this.props;
const {originalVotes, votes, voteOrder} = this.state;
const {fromWei} = web3.utils;
render() {
const { polls, classes, balances, idPoll, back, decimals } = this.props;
const { originalVotes, votes, voteOrder } = this.state;
const { fromWei } = web3.utils;
if(!polls){
if (!polls) {
return null;
}
const symbol = this.props.symbol;
const poll = polls.find(p => p.idPoll == idPoll);
if(!poll) return null;
if (!poll) return null;
const title = poll.content.title;
const ballots = poll.content.ballots
const ballots = poll.content.ballots;
const balance = utils.fromTokenDecimals(balances[idPoll].tokenBalance, decimals);
const cantVote = balance == 0 || !poll._canVote;
const availableCredits = parseInt(balance, 10) - votes.reduce((prev, curr) => prev + curr * curr, 0);
const disableVote = cantVote || votes.reduce((x,y) => x+y, 0) == 0;
const disableVote = cantVote || votes.reduce((x, y) => x + y, 0) == 0;
// Votes calculation
const originalVotesQty = originalVotes.reduce((x,y) => x+y, 0);
const originalVotesQty = originalVotes.reduce((x, y) => x + y, 0);
// Calculating votes availables
const maxVotes = Math.floor(Math.sqrt(balance));
const maxValuesForBallots = [];
let votedTokens = 0;
for(let i = 0; i < poll._numBallots; i++){
if(votes[i] == undefined){
for (let i = 0; i < poll._numBallots; i++) {
if (votes[i] == undefined) {
votes[i] = 0;
} else {
votedTokens += votes[i]*votes[i];
votedTokens += votes[i] * votes[i];
}
}
for(let i = 0; i < poll._numBallots; i++){
maxValuesForBallots[i] = Math.floor(Math.sqrt(balance - votedTokens + votes[i]*votes[i]));
for (let i = 0; i < poll._numBallots; i++) {
maxValuesForBallots[i] = Math.floor(Math.sqrt(balance - votedTokens + votes[i] * votes[i]));
}
return <Fragment>
<div className="section">
<Typography variant="headline">{title}</Typography>
{ voteOrder.map((v, i) => {
const item = ballots[v];
return <BallotSlider key={i} title={item.title} subtitle={item.subtitle} symbol={symbol} classes={classes} votes={votes[v]} cantVote={cantVote} balance={balance} maxVotes={maxVotes} maxVotesAvailable={maxValuesForBallots[v]} updateVotes={this.updateVotes(v)} />
})}
</div>
<div className="buttonNav">
<Typography className="votingCredits"><span>{availableCredits}</span> Credits left</Typography>
<Button disabled={disableVote} variant="text" onClick={this.sendToReview}>Review vote</Button>
</div>
</Fragment>
return (
<Fragment>
<div className="section">
<Typography variant="headline">{title}</Typography>
{voteOrder.map((v, i) => {
const item = ballots[v];
return (
<BallotSlider
key={i}
title={item.title}
subtitle={item.subtitle}
symbol={symbol}
classes={classes}
votes={votes[v]}
cantVote={cantVote}
balance={balance}
maxVotes={maxVotes}
maxVotesAvailable={maxValuesForBallots[v]}
updateVotes={this.updateVotes(v)}
/>
);
})}
</div>
<div className="buttonNav">
<Typography className="votingCredits">
<span>{availableCredits}</span> Credits left
</Typography>
<Button disabled={disableVote} variant="text" onClick={this.sendToReview}>
Review vote
</Button>
</div>
</Fragment>
);
}
}
class BallotSlider extends Component {
state = {
value: 0,
interval: null
}
};
componentDidMount(){
this.setState({value: this.props.votes || 0});
componentDidMount() {
this.setState({ value: this.props.votes || 0 });
}
/*
@ -181,95 +194,109 @@ class BallotSlider extends Component {
this.props.updateVotes(value);
};*/
increaseVotes = (event) => {
increaseVotes = event => {
this.removeInterval();
const updateVotes = () => {
let value = this.state.value;
if(value + 1 > this.props.maxVotesAvailable){
if (value + 1 > this.props.maxVotesAvailable) {
value = this.props.maxVotesAvailable;
} else {
value++;
}
this.setState({value});
this.setState({ value });
this.props.updateVotes(value);
};
updateVotes();
const interval = setInterval(updateVotes, 150);
this.setState({interval});
}
this.setState({ interval });
};
reduceVotes = (event) => {
reduceVotes = event => {
this.removeInterval();
const updateVotes = () => {
let value = this.state.value;
if(value - 1 < 0){
if (value - 1 < 0) {
value = 0;
} else {
} else {
value--;
}
this.setState({value});
this.setState({ value });
this.props.updateVotes(value);
};
updateVotes();
const interval = setInterval(updateVotes, 150);
this.setState({interval});
}
this.setState({ interval });
};
removeInterval = () => {
clearInterval(this.state.interval);
this.setState({interval: null});
this.setState({ interval: null });
};
}
render(){
const {maxVotes, maxVotesAvailable, classes, cantVote, balance, symbol, title, subtitle} = this.props;
const {value} = this.state;
render() {
const { maxVotes, maxVotesAvailable, classes, cantVote, balance, symbol, title, subtitle } = this.props;
const { value } = this.state;
const nextVote = value + 1;
const toBN = web3.utils.toBN;
let percentage = Math.round(value * 100 / maxVotes);
percentage = percentage > 100 ? 100 : percentage;
let percentage = Math.round((value * 100) / maxVotes);
percentage = percentage > 100 ? 100 : percentage;
return <Card className="card">
<CardContent>
<Typography gutterBottom component="h2">{title}</Typography>
<Typography component="p">{subtitle}</Typography>
<div className="customSlider">
<div className="nav1">
<button onMouseDown={this.reduceVotes} onMouseUp={this.removeInterval} onMouseLeave={this.removeInterval} disabled={percentage == 0 || cantVote || value == 0}><img src="images/slider-minus.svg" /></button>
</div>
<div className="nav2">
<button onMouseDown={this.increaseVotes} onMouseUp={this.removeInterval} onMouseLeave={this.removeInterval} disabled={percentage == 100 || cantVote || nextVote > maxVotesAvailable}><img src="images/slider-plus.svg" /></button>
</div>
<div className="content">
<div className="progress progress-large">
<span style={{width: percentage +'%'}}>
<Typography gutterBottom component="h2"><span>{value} votes <br /><small>{value*value} Credits</small></span></Typography>
</span>
</div>
</div>
<div className="clear"></div>
return (
<Card className="card">
<CardContent>
<Typography gutterBottom component="h2">
{title}
</Typography>
<Typography component="p">{subtitle}</Typography>
<div className="customSlider">
<div className="nav1">
<button
onMouseDown={this.reduceVotes}
onMouseUp={this.removeInterval}
onMouseLeave={this.removeInterval}
disabled={percentage == 0 || cantVote || value == 0}
>
<img src="images/slider-minus.svg" />
</button>
</div>
<div className="nav2">
<button
onMouseDown={this.increaseVotes}
onMouseUp={this.removeInterval}
onMouseLeave={this.removeInterval}
disabled={percentage == 100 || cantVote || nextVote > maxVotesAvailable}
>
<img src="images/slider-plus.svg" />
</button>
</div>
<div className="content">
<div className="progress progress-large">
<span style={{ width: percentage + '%' }}>
<Typography gutterBottom component="h2">
<span>
{value} votes <br />
<small>{value * value} Credits</small>
</span>
</Typography>
</span>
</div>
</CardContent>
</Card>;
</div>
<div className="clear" />
</div>
</CardContent>
</Card>
);
}
}
// <Slider disabled={cantVote} classes={{ thumb: classes.thumb }} style={{ width: '95%' }} value={value} min={0} max={maxVotes} step={1} onChange={this.handleChange} />
// {balance > 0 && !cantVote && <b>Your votes: {value} ({value * value} {symbol})</b>}
// { nextVote <= maxVotesAvailable && !cantVote ? <small>- Additional vote will cost {nextVote*nextVote - value*value} {symbol}</small> : (balance > 0 && !cantVote && <small>- Not enough balance available to buy additional votes</small>) }
export default withRouter(withStyles(styles)(PollVoting));

View File

@ -1,184 +1,230 @@
import {Link} from "react-router-dom";
import { Link } from 'react-router-dom';
import Button from '@material-ui/core/Button';
import React, {Component, Fragment} from 'react';
import Typography from '@material-ui/core/Typography'
import React, { Component, Fragment } from 'react';
import Typography from '@material-ui/core/Typography';
import PollManager from 'Embark/contracts/PollManager';
import utils from '../../utils/utils';
class Results extends Component {
state = {
isError: false,
poll: null,
isPending: true,
netId: 3
};
}
constructor(props){
constructor(props) {
super(props);
if(props.polls)
this.state.poll = props.polls.find(p => p.idPoll == props.idPoll);
if (props.polls) this.state.poll = props.polls.find(p => p.idPoll == props.idPoll);
}
componentDidUpdate(prevProps){
componentDidUpdate(prevProps) {
if (this.props.idPoll !== prevProps.idPoll) {
this.updatePoll();
}
if(this.props.polls !== prevProps.polls){
if (this.props.polls !== prevProps.polls) {
const poll = this.props.polls.find(p => p.idPoll == this.props.idPoll);
if(poll && !poll.content){
if (poll && !poll.content) {
this.props.loadPollContent(poll);
}
}
}
updatePoll(){
const idPoll = this.props.idPoll;
updatePoll() {
const idPoll = this.props.idPoll;
PollManager.methods.poll(idPoll).call().then(poll => {
this.setState({poll});
})
PollManager.methods
.poll(idPoll)
.call()
.then(poll => {
this.setState({ poll });
});
}
componentDidMount(){
const {transaction, idPoll, transactionHash} = this.props;
componentDidMount() {
const { transaction, idPoll, transactionHash } = this.props;
EmbarkJS.onReady(() => {
this.updatePoll();
web3.eth.net.getId((err, netId) => {
this.setState({netId});
this.setState({ netId });
});
if(transaction[idPoll]){
transaction[idPoll].catch(x => {
this.setState({isError: true});
}).then(() => {
this.updatePoll();
})
if (transaction[idPoll]) {
transaction[idPoll]
.catch(error => {
this.setState({ isError: true });
})
.then(() => {
this.updatePoll();
});
let req = false;
let interval = setInterval(async () => {
if(req || !transactionHash[idPoll]) return;
if (req || !transactionHash[idPoll]) return;
req = true;
try {
const receipt = await web3.eth.getTransactionReceipt(transactionHash[idPoll]);
if(receipt){
if (receipt) {
clearInterval(interval);
if(receipt.status || receipt.status == "0x1"){
this.setState({isPending: false});
if (receipt.status || receipt.status == '0x1') {
this.setState({ isPending: false });
this.updatePoll();
} else {
this.setState({isError: true});
this.setState({ isError: true });
}
}
} catch(e){
}
} catch (error) {}
req = false;
}, 100);
}
});
}
render(){
const {polls, idPoll, transaction, transactionHash} = this.props;
let {isError, poll, isPending, netId} = this.state;
if(!poll || !poll){
render() {
const { polls, idPoll, transaction, transactionHash } = this.props;
let { isError, poll, isPending, netId } = this.state;
if (!poll || !poll) {
return null;
}
const p = polls.find(p => p.idPoll == idPoll);
if(!p || !p.content) return null;
if (!p || !p.content) return null;
const title = p.content.title;
const ballots = p.content.ballots;
const totalVotes = poll._quadraticVotes.map(x => parseInt(x, 10)).reduce((x, y) => x + y, 0);
const etherscanURL = netId == 3 ? 'https://ropsten.etherscan.io/tx/' : ( netId == 1 ? "https://etherscan.io/tx/" : '');
const etherscanURL = netId == 3 ? 'https://ropsten.etherscan.io/tx/' : netId == 1 ? 'https://etherscan.io/tx/' : '';
return <Fragment>
{ isError && <div className="errorTrx">
<div className="image"><img src="images/sad-face.svg" width="24" /></div>
<Typography variant="headline">Transaction failed</Typography>
<Typography variant="body1">Your transaction failed to be written to the blockchain. This is usually because of network congestion. Please try again</Typography>
<Link to={"/review/" + idPoll}>
<Button color="primary" variant="contained">Try again</Button>
</Link>
</div> }
{ !isError && transaction[idPoll] && <div className="transactionArea">
{ isPending && <div className="pending">
<div className="spinner">
<div className="bounce1"></div>
<div className="bounce2"></div>
<div className="bounce3"></div>
return (
<Fragment>
{isError && (
<div className="errorTrx">
<div className="image">
<img src="images/sad-face.svg" width="24" />
</div>
<Typography variant="headline">Transaction failed</Typography>
<Typography variant="body1">
Your transaction failed to be written to the blockchain. This is usually because of network congestion.
Please try again
</Typography>
<Link to={'/review/' + idPoll}>
<Button color="primary" variant="contained">
Try again
</Button>
</Link>
</div>
<Typography variant="headline">Your vote will be posted once the transaction is complete.</Typography>
<Typography variant="body1">Your vote is in the process of being confirmed in the blockchain</Typography>
)}
{!isError && transaction[idPoll] && (
<div className="transactionArea">
{isPending && (
<div className="pending">
<div className="spinner">
<div className="bounce1" />
<div className="bounce2" />
<div className="bounce3" />
</div>
<Typography variant="headline">Your vote will be posted once the transaction is complete.</Typography>
<Typography variant="body1">
Your vote is in the process of being confirmed in the blockchain
</Typography>
</div>
)}
{!isPending && transaction[idPoll] && (
<div className="confirmed">
<img src="images/confirmed.svg" width="40" />
<Typography variant="headline">
Transaction confirmed!
<br />
Your vote was posted.
</Typography>
</div>
)}
{transactionHash[idPoll] && etherscanURL && (
<Typography variant="body1">
<a target="_blank" href={etherscanURL + transactionHash[idPoll]}>
View details on Etherscan
</a>
</Typography>
)}
</div>
)}
<div className="section">
{!isError && (
<Fragment>
<Typography variant="headline" gutterBottom>
{title}
</Typography>
{ballots.map((item, i) => (
<BallotResult
title={item.title}
symbol={this.props.symbol}
decimals={this.props.decimals}
totalVotes={totalVotes}
quadraticVotes={poll._quadraticVotes[i]}
tokenTotal={poll._tokenTotal[i]}
totalVoters={poll._votersByBallot[i]}
key={i}
/>
))}
</Fragment>
)}
</div>
}
{ !isPending && transaction[idPoll] && <div className="confirmed">
<img src="images/confirmed.svg" width="40" />
<Typography variant="headline">Transaction confirmed!<br />
Your vote was posted.</Typography>
</div>}
{ transactionHash[idPoll] && etherscanURL && <Typography variant="body1"><a target="_blank" href={ etherscanURL + transactionHash[idPoll]}>View details on Etherscan</a></Typography> }
</div>
}
<div className="section">
{ !isError && <Fragment>
<Typography variant="headline" gutterBottom>{title}</Typography>
{ ballots.map((item, i) => <BallotResult title={item.title} symbol={this.props.symbol} decimals={this.props.decimals} totalVotes={totalVotes} quadraticVotes={poll._quadraticVotes[i]} tokenTotal={poll._tokenTotal[i]} totalVoters={poll._votersByBallot[i]} key={i} />) }
</Fragment>
}
</div>
</Fragment>;
</Fragment>
);
}
}
class BallotResult extends Component {
state = {
show: false
}
};
showDetails = () => {
const show = this.state.show;
this.setState({show: !show});
}
this.setState({ show: !show });
};
render(){
const {title, quadraticVotes, tokenTotal, totalVotes, totalVoters, decimals} = this.props;
const {show} = this.state;
render() {
const { title, quadraticVotes, tokenTotal, totalVotes, totalVoters, decimals } = this.props;
const { show } = this.state;
const votePercentage = totalVotes > 0 ? parseInt(quadraticVotes) / totalVotes * 100 : 0;
const votePercentage = totalVotes > 0 ? (parseInt(quadraticVotes) / totalVotes) * 100 : 0;
const totalInUnits = utils.fromTokenDecimals(tokenTotal, decimals);
return (<Fragment>
<Typography gutterBottom component="h2" className="ballotResultTitle" onClick={this.showDetails}>{title}</Typography>
<div className="ballotResult" onClick={this.showDetails}>
<div className="progress progress-large result-progress" onClick={this.showDetails}>
<span style={{width: votePercentage +'%'}}></span>
return (
<Fragment>
<Typography gutterBottom component="h2" className="ballotResultTitle" onClick={this.showDetails}>
{title}
</Typography>
<div className="ballotResult" onClick={this.showDetails}>
<div className="progress progress-large result-progress" onClick={this.showDetails}>
<span style={{ width: votePercentage + '%' }} />
</div>
<span className={show ? 'collapse percentage' : 'percentage'}>{votePercentage.toFixed(2)}%</span>
</div>
<span className={show ? 'collapse percentage' : 'percentage'}>{votePercentage.toFixed(2)}%</span>
</div>
{show && <ul className="ballotResultData">
<Typography component="li">Voters: <span>{totalVoters}</span></Typography>
<Typography component="li">Total votes: <span>{quadraticVotes}</span></Typography>
<Typography component="li" className="noBorder">Total {this.props.symbol}: <span>{totalInUnits}</span></Typography>
</ul>}
</Fragment>);
{show && (
<ul className="ballotResultData">
<Typography component="li">
Voters: <span>{totalVoters}</span>
</Typography>
<Typography component="li">
Total votes: <span>{quadraticVotes}</span>
</Typography>
<Typography component="li" className="noBorder">
Total {this.props.symbol}: <span>{totalInUnits}</span>
</Typography>
</ul>
)}
</Fragment>
);
}
}

View File

@ -1,30 +1,28 @@
import {Link} from "react-router-dom";
import { Link } from 'react-router-dom';
import Button from '@material-ui/core/Button';
import React, {Component, Fragment} from 'react';
import Typography from '@material-ui/core/Typography'
import React, { Component, Fragment } from 'react';
import Typography from '@material-ui/core/Typography';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import PollManager from 'Embark/contracts/PollManager';
import { withRouter } from 'react-router-dom'
import { withRouter } from 'react-router-dom';
import utils from '../../utils/utils';
class ReviewVotes extends Component {
state = {
isSubmitting: false
}
};
vote = () => {
this.setState({isSubmitting: true});
this.setState({ isSubmitting: true });
const { vote, unvote } = PollManager.methods;
const { votes, history, idPoll, decimals} = this.props;
const { votes, history, idPoll, decimals } = this.props;
const { toWei, toBN } = web3.utils;
const seconds = this.props.polls.find(p => p.idPoll == this.props.idPoll)._endTime - (new Date()).getTime() / 1000;
if(seconds <= 0){
alert("Poll is expired");
const seconds = this.props.polls.find(p => p.idPoll == this.props.idPoll)._endTime - new Date().getTime() / 1000;
if (seconds <= 0) {
alert('Poll is expired');
this.props.history.push('/');
return;
}
@ -35,94 +33,105 @@ class ReviewVotes extends Component {
return utils.toTokenDecimals(num.toString(), decimals);
});
const balance4Voting = ballots.reduce((prev, curr) => toBN(prev).add(toBN(curr)), toBN("0"));
const balance4Voting = ballots.reduce((prev, curr) => toBN(prev).add(toBN(curr)), toBN('0'));
const toSend = balance4Voting == 0 ? unvote(idPoll) : vote(idPoll, ballots.map(x => x.toString()));
toSend.estimateGas()
.then(gasEstimated => {
console.log("voting gas estimated: " + gasEstimated);
toSend.estimateGas().then(gasEstimated => {
console.log('voting gas estimated: ' + gasEstimated);
const transaction = toSend.send({gas: gasEstimated + 100000});
transaction.on('transactionHash', hash => {
this.props.setTransactionHash(idPoll, hash);
this.props.setTransactionPromise(idPoll, transaction);
history.push('/results/' + idPoll);
});
transaction.catch(err => {
this.setState({isSubmitting: false});
})
const transaction = toSend.send({ gas: gasEstimated + 100000 });
transaction.on('transactionHash', hash => {
this.props.setTransactionHash(idPoll, hash);
this.props.setTransactionPromise(idPoll, transaction);
history.push('/results/' + idPoll);
});
}
componentDidMount(){
const {polls, originalVotes, idPoll, history} = this.props;
transaction.catch(error => {
this.setState({ isSubmitting: false });
});
});
};
if(!polls){
componentDidMount() {
const { polls, originalVotes, idPoll, history } = this.props;
if (!polls) {
history.push('/');
return;
}
const poll = polls.find(p => p.idPoll == idPoll);
if(!poll) {
if (!poll) {
history.push('/');
return;
};
}
}
render(){
const {polls, balances, votes, idPoll, decimals} = this.props;
const {isSubmitting} = this.state;
const {fromWei} = web3.utils;
render() {
const { polls, balances, votes, idPoll, decimals } = this.props;
const { isSubmitting } = this.state;
const { fromWei } = web3.utils;
if(!polls|| !balances[idPoll]){
if (!polls || !balances[idPoll]) {
return null;
}
const poll = polls.find(p => p.idPoll == idPoll);;
if(!poll) return null;
const ballots = poll.content.ballots
const poll = polls.find(p => p.idPoll == idPoll);
if (!poll) return null;
const ballots = poll.content.ballots;
const balance = utils.fromTokenDecimals(balances[idPoll].tokenBalance, decimals);
const availableCredits = parseInt(balance, 10) - votes.reduce((prev, curr) => prev + curr * curr, 0);
return (polls ? <Fragment><div className="section">
<Typography variant="headline">Review your vote</Typography>
{ ballots.map((item, i) => {
return votes[i] > 0 ? <Card className="card review" key={i}>
return polls ? (
<Fragment>
<div className="section">
<Typography variant="headline">Review your vote</Typography>
{ballots.map((item, i) => {
return votes[i] > 0 ? (
<Card className="card review" key={i}>
<CardContent>
<Typography gutterBottom component="h2">
{item.title}
</Typography>
<Typography component="p">{item.subtitle}</Typography>
<div className="data">
<div className="item">
<span>{votes[i]}</span>
votes
</div>
<div className="item noBorder">
<span>{votes[i] * votes[i]}</span>
credits
</div>
</div>
</CardContent>
</Card>
) : null;
})}
<Card className="card creditsAvailable">
<CardContent>
<Typography gutterBottom component="h2">{item.title}</Typography>
<Typography component="p">{item.subtitle}</Typography>
<div className="data">
<div className="item">
<span>{votes[i]}</span>
votes
</div>
<div className="item noBorder">
<span>{votes[i] * votes[i]}</span>
credits
</div>
</div>
<Typography gutterBottom component="p">
Unused voting power
</Typography>
<Typography gutterBottom component="h2">
{availableCredits} credits
</Typography>
<Link to={'/voting/' + idPoll + '/back'}>Add votes</Link>
</CardContent>
</Card> : null;
})}
<Card className="card creditsAvailable">
<CardContent>
<Typography gutterBottom component="p">Unused voting power</Typography>
<Typography gutterBottom component="h2">{availableCredits} credits</Typography>
<Link to={"/voting/" + idPoll + "/back"}>Add votes</Link>
</CardContent>
</Card>
</div>
<div className="buttonNav">
<Button variant="text" onClick={this.vote} disabled={isSubmitting}>Sign to confirm</Button>
</div></Fragment> : null);
</Card>
</div>
<div className="buttonNav">
<Button variant="text" onClick={this.vote} disabled={isSubmitting}>
Sign to confirm
</Button>
</div>
</Fragment>
) : null;
}
}

View File

@ -1,25 +1,24 @@
import {Link} from "react-router-dom";
import { Link } from 'react-router-dom';
import Button from '@material-ui/core/Button';
import React, {Component, Fragment} from 'react';
import Typography from '@material-ui/core/Typography'
import React, { Component, Fragment } from 'react';
import Typography from '@material-ui/core/Typography';
const pad = (n, width, z) => {
z = z || '0';
n = n + '';
n = String(n);
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}
};
class TitleScreen extends Component {
state = {
time: {},
seconds: -1,
initTimer: false
}
};
timer = 0
timer = 0;
secondsToTime(secs){
secondsToTime(secs) {
let days = Math.floor(secs / 86400);
let divisor_for_hours = secs % 86400;
let hours = Math.floor(divisor_for_hours / (60 * 60));
@ -27,9 +26,9 @@ class TitleScreen extends Component {
let minutes = Math.floor(divisor_for_minutes / 60);
let obj = {
"d": days,
"h": hours,
"m": minutes
d: days,
h: hours,
m: minutes
};
return obj;
@ -45,111 +44,141 @@ class TitleScreen extends Component {
let seconds = this.state.seconds - 1;
this.setState({
time: this.secondsToTime(seconds),
seconds: seconds,
seconds: seconds
});
if (seconds == 0) {
if (seconds == 0) {
clearInterval(this.timer);
}
}
componentWillUnmount(){
componentWillUnmount() {
clearInterval(this.timer);
}
componentDidMount(){
if(this.props.polls){
componentDidMount() {
if (this.props.polls) {
this.initTimer();
}
}
componentDidUpdate(prevProps){
componentDidUpdate(prevProps) {
if (this.props.polls !== prevProps.polls && this.props.polls) {
this.initTimer();
// TODO: see how to extract this. Maybe a higher order component?
const poll = this.props.polls.find(p => p.idPoll == this.props.idPoll);
if(poll && !poll.content){
if (poll && !poll.content) {
this.props.loadPollContent(poll);
}
}
}
initTimer(){
if(!this.props.polls || !this.props.polls.length) return;
initTimer() {
if (!this.props.polls || !this.props.polls.length) return;
if(this.state.initTimer) return;
if (this.state.initTimer) return;
this.setState({initTimer: true});
this.setState({ initTimer: true });
const poll = this.props.polls.find(p => p.idPoll == this.props.idPoll);
const seconds = poll._endTime - (new Date()).getTime() / 1000
if(seconds > 0){
const seconds = poll._endTime - new Date().getTime() / 1000;
if (seconds > 0) {
let timeLeftVar = this.secondsToTime(seconds);
this.setState({ time: timeLeftVar, seconds });
this.startTimer();
} else {
this.setState({seconds});
this.setState({ seconds });
}
}
render(){
const {time, seconds} = this.state;
const {polls, idPoll} = this.props;
render() {
const { time, seconds } = this.state;
const { polls, idPoll } = this.props;
if (!polls || !polls.length) return null;
if(!polls || !polls.length) return null;
const poll = polls.find(p => p.idPoll == idPoll);
if(!poll) return null;
if (!poll) return null;
if(!poll.content) return null;
if (!poll.content) return null;
const title = poll.content.title;
const description = poll.content.description;
const canceled = poll._canceled;
return <Fragment>
<div>
{!canceled && <div>
<div className="section" style={{marginBottom: 0}}>
<img src="images/status-logo.svg" width="36" />
<Typography variant="headline">{title}</Typography>
<Typography variant="body1" component="div" style={{marginTop: '24px'}} dangerouslySetInnerHTML={{__html: description}}></Typography>
</div>
<hr />
{ seconds > 0 && <div className="votingTimer">
<Typography variant="body1">Voting ends in</Typography>
<ul>
<li>
<Typography variant="headline">{pad(time.d, 2)}</Typography>
<Typography variant="body1" className="timeUnit">Days</Typography>
</li>
<li>
<Typography variant="headline">{pad(time.h, 2)}</Typography>
<Typography variant="body1" className="timeUnit">Hours</Typography>
</li>
<li>
<Typography variant="headline">{pad(time.m, 2)}</Typography>
<Typography variant="body1" className="timeUnit">Mins</Typography>
</li>
</ul>
<div className="action">
<Link to={"/learn/" + idPoll}><Button variant="contained" color="primary">Get started</Button></Link><br />
<p><Link to={"/results/" + idPoll}>See ongoing results</Link></p>
return (
<Fragment>
<div>
{!canceled && (
<div>
<div className="section" style={{ marginBottom: 0 }}>
<img src="images/status-logo.svg" width="36" />
<Typography variant="headline">{title}</Typography>
<Typography
variant="body1"
component="div"
style={{ marginTop: '24px' }}
dangerouslySetInnerHTML={{ __html: description }}
/>
</div>
<hr />
{seconds > 0 && (
<div className="votingTimer">
<Typography variant="body1">Voting ends in</Typography>
<ul>
<li>
<Typography variant="headline">{pad(time.d, 2)}</Typography>
<Typography variant="body1" className="timeUnit">
Days
</Typography>
</li>
<li>
<Typography variant="headline">{pad(time.h, 2)}</Typography>
<Typography variant="body1" className="timeUnit">
Hours
</Typography>
</li>
<li>
<Typography variant="headline">{pad(time.m, 2)}</Typography>
<Typography variant="body1" className="timeUnit">
Mins
</Typography>
</li>
</ul>
<div className="action">
<Link to={'/learn/' + idPoll}>
<Button variant="contained" color="primary">
Get started
</Button>
</Link>
<br />
<p>
<Link to={'/results/' + idPoll}>See ongoing results</Link>
</p>
</div>
</div>
)}
{seconds < 0 && (
<div className="pollClosed">
<Typography variant="headline">Poll closed</Typography>
<Typography variant="body1">
The vote was finished {parseInt(Math.abs(seconds) / 86400, 10)} day(s) ago
</Typography>
<div className="action">
<Link to={'/results/' + idPoll}>
<Button variant="contained" color="primary">
View results
</Button>
</Link>
</div>
</div>
)}
</div>
)}
</div>
</div>}
{ seconds < 0 && <div className="pollClosed">
<Typography variant="headline">Poll closed</Typography>
<Typography variant="body1">The vote was finished {parseInt(Math.abs(seconds) / 86400, 10)} day(s) ago</Typography>
<div className="action">
<Link to={"/results/" + idPoll}><Button variant="contained" color="primary">View results</Button></Link>
</div>
</div> }
</div>}
</div>
</Fragment>
;
</Fragment>
);
}
}

View File

@ -1,34 +1,32 @@
import {Link} from "react-router-dom";
import { Link } from 'react-router-dom';
import Button from '@material-ui/core/Button';
import React, {Component, Fragment} from 'react';
import Typography from '@material-ui/core/Typography'
import React, { Component, Fragment } from 'react';
import Typography from '@material-ui/core/Typography';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import { withRouter } from 'react-router-dom'
import { withRouter } from 'react-router-dom';
import HelpDialog from './HelpDialog';
import utils from '../../utils/utils';
Date.prototype.DDMMYYYY = function () {
Date.prototype.DDMMYYYY = function() {
var yyyy = this.getFullYear().toString();
var MM = pad(this.getMonth() + 1,2);
var MM = pad(this.getMonth() + 1, 2);
var dd = pad(this.getDate(), 2);
return dd + '/' + MM + '/' + yyyy ;
return dd + '/' + MM + '/' + yyyy;
};
function pad(number, length) {
var str = '' + number;
var str = String(number);
while (str.length < length) {
str = '0' + str;
str = '0' + str;
}
return str;
}
class VotingCredits extends Component {
state = {
open: false,
open: false
};
handleClickOpen = () => {
@ -39,18 +37,18 @@ class VotingCredits extends Component {
this.setState({ open: false });
};
componentDidMount(){
const {polls, balances, history} = this.props;
if(!polls || !balances || !balances.length){
componentDidMount() {
const { polls, balances, history } = this.props;
if (!polls || !balances || !balances.length) {
history.push('/');
}
}
componentDidUpdate(prevProps){
componentDidUpdate(prevProps) {
if (this.props.polls !== prevProps.polls && this.props.polls) {
// TODO: see how to extract this. Maybe a higher order component?
const poll = this.props.polls.find(p => p.idPoll == this.props.idPoll);
if(poll && !poll.content){
if (poll && !poll.content) {
this.props.loadPollContent(poll);
}
}
@ -58,73 +56,91 @@ class VotingCredits extends Component {
redirectToConnect = () => {
this.props.history.push('/wallet/' + this.props.idPoll);
}
};
render(){
const {polls, balances, idPoll, decimals} = this.props;
render() {
const { polls, balances, idPoll, decimals } = this.props;
if(!polls || !balances) return null;
if (!polls || !balances) return null;
const poll = polls.find(p => p.idPoll == idPoll);
if(!poll || !poll.content) return null;
if (!poll || !poll.content) return null;
let title = poll.content.title;
let description = poll.content.description;
let ethBalance = web3.utils.fromWei(balances[idPoll].ethBalance, "ether");
let ethBalance = web3.utils.fromWei(balances[idPoll].ethBalance, 'ether');
let tokenBalance = Math.floor(utils.fromTokenDecimals(balances[idPoll].tokenBalance, decimals));
const d = new Date(poll.blockInfo.timestamp * 1000);
const d = new Date(poll.blockInfo.timestamp * 1000);
return (polls ? <Fragment><div className="section">
<Typography variant="headline">{title}</Typography>
<Typography variant="body1" component="div" dangerouslySetInnerHTML={{__html: description}}></Typography>
<Card className="card credits" onClick={this.redirectToConnect}>
<CardContent>
<Typography component="div" onClick={this.redirectToConnect}>
<span className="title">Voting Credits</span>
<span className="value">{tokenBalance}</span>
</Typography>
{ tokenBalance >= 1 &&
<Typography component="p" className="text" onClick={this.redirectToConnect}>
You get one credit for each {this.props.symbol} held in your wallet <b>at the time of poll was created ({d.DDMMYYYY()})</b>. They are usable only in this poll.
</Typography> }
{ tokenBalance < 1 &&
<div className="warning">
<Typography component="h2" onClick={this.redirectToConnect}>
No {this.props.symbol} in your wallet
</Typography>
<Typography component="p" onClick={this.redirectToConnect}>
To vote, you need to connect with a wallet that holds {this.props.symbol} tokens <b>when the poll was created ({d.DDMMYYYY()})</b>.
</Typography>
</div>
}
</CardContent>
</Card>
{ ethBalance == 0 && <Card className="card credits">
<CardContent>
<Typography component="div" onClick={this.redirectToConnect}>
<span className="title">ETH</span>
<span className="value">{ethBalance}</span>
</Typography>
<div className="warning">
<Typography component="h2" onClick={this.redirectToConnect}>
Not enough ETH in your wallet
return polls ? (
<Fragment>
<div className="section">
<Typography variant="headline">{title}</Typography>
<Typography variant="body1" component="div" dangerouslySetInnerHTML={{ __html: description }} />
<Card className="card credits" onClick={this.redirectToConnect}>
<CardContent>
<Typography component="div" onClick={this.redirectToConnect}>
<span className="title">Voting Credits</span>
<span className="value">{tokenBalance}</span>
</Typography>
<Typography component="p" onClick={this.redirectToConnect}>
You will sign a transaction to confirm your vote. No tokens are sent, but you need ETH to pay for gas (Ethereum network fee).
</Typography>
</div>
</CardContent>
</Card> }
</div>
<div className={(ethBalance == 0 || tokenBalance == 0) ? 'buttonNav back' : 'buttonNav'}>
{ (ethBalance == 0 || tokenBalance == 0) && <Link to={"/wallet/" + idPoll}><Button variant="text">Back</Button></Link> }
{ (ethBalance > 0 && tokenBalance > 0) && <Link to={"/voting/" + idPoll}><Button variant="text">Vote</Button></Link> }
</div>
<p className="helpLink">Need help? <a onClick={this.handleClickOpen}>Chat with us</a></p>
<HelpDialog open={this.state.open} symbol={this.props.symbol} handleClose={this.handleClose} />
</Fragment> : null);
{tokenBalance >= 1 && (
<Typography component="p" className="text" onClick={this.redirectToConnect}>
You get one credit for each {this.props.symbol} held in your wallet{' '}
<b>at the time of poll was created ({d.DDMMYYYY()})</b>. They are usable only in this poll.
</Typography>
)}
{tokenBalance < 1 && (
<div className="warning">
<Typography component="h2" onClick={this.redirectToConnect}>
No {this.props.symbol} in your wallet
</Typography>
<Typography component="p" onClick={this.redirectToConnect}>
To vote, you need to connect with a wallet that holds {this.props.symbol} tokens{' '}
<b>when the poll was created ({d.DDMMYYYY()})</b>.
</Typography>
</div>
)}
</CardContent>
</Card>
{ethBalance == 0 && (
<Card className="card credits">
<CardContent>
<Typography component="div" onClick={this.redirectToConnect}>
<span className="title">ETH</span>
<span className="value">{ethBalance}</span>
</Typography>
<div className="warning">
<Typography component="h2" onClick={this.redirectToConnect}>
Not enough ETH in your wallet
</Typography>
<Typography component="p" onClick={this.redirectToConnect}>
You will sign a transaction to confirm your vote. No tokens are sent, but you need ETH to pay for
gas (Ethereum network fee).
</Typography>
</div>
</CardContent>
</Card>
)}
</div>
<div className={ethBalance == 0 || tokenBalance == 0 ? 'buttonNav back' : 'buttonNav'}>
{(ethBalance == 0 || tokenBalance == 0) && (
<Link to={'/wallet/' + idPoll}>
<Button variant="text">Back</Button>
</Link>
)}
{ethBalance > 0 && tokenBalance > 0 && (
<Link to={'/voting/' + idPoll}>
<Button variant="text">Vote</Button>
</Link>
)}
</div>
<p className="helpLink">
Need help? <a onClick={this.handleClickOpen}>Chat with us</a>
</p>
<HelpDialog open={this.state.open} symbol={this.props.symbol} handleClose={this.handleClose} />
</Fragment>
) : null;
}
}

View File

@ -1,32 +1,31 @@
import {Link} from "react-router-dom";
import { Link } from 'react-router-dom';
import Button from '@material-ui/core/Button';
import React, {Component, Fragment} from 'react';
import Typography from '@material-ui/core/Typography'
import React, { Component, Fragment } from 'react';
import Typography from '@material-ui/core/Typography';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import { withRouter } from 'react-router-dom'
import { withRouter } from 'react-router-dom';
import HelpDialog from '../HelpDialog';
import DappToken from 'Embark/contracts/DappToken';
import DappToken from 'Embark/contracts/DappToken';
import utils from '../../../utils/utils';
Date.prototype.DDMMYYYY = function () {
Date.prototype.DDMMYYYY = function() {
var yyyy = this.getFullYear().toString();
var MM = pad(this.getMonth() + 1,2);
var MM = pad(this.getMonth() + 1, 2);
var dd = pad(this.getDate(), 2);
return dd + '/' + MM + '/' + yyyy ;
return dd + '/' + MM + '/' + yyyy;
};
function pad(number, length) {
var str = '' + number;
var str = String(number);
while (str.length < length) {
str = '0' + str;
str = '0' + str;
}
return str;
}
class PollCreationCredits extends Component {
state = {
open: false,
tokenBalance: '-',
@ -41,84 +40,95 @@ class PollCreationCredits extends Component {
this.setState({ open: false });
};
redirectToConnect = () => {
this.props.history.push('/wallet/poll-creation');
}
};
componentDidMount(){
const {history, decimals} = this.props;
componentDidMount() {
const { history, decimals } = this.props;
if(this.props.poll.title !== undefined){
if (this.props.poll.title !== undefined) {
this.props.resetPoll();
history.push('/');
return;
}
EmbarkJS.onReady(async () => {
const tokenBalance = await DappToken.methods.balanceOf(web3.eth.defaultAccount).call({from: web3.eth.defaultAccount});
const tokenBalance = await DappToken.methods
.balanceOf(web3.eth.defaultAccount)
.call({ from: web3.eth.defaultAccount });
const ethBalance = await web3.eth.getBalance(web3.eth.defaultAccount);
this.setState({tokenBalance, ethBalance});
this.setState({ tokenBalance, ethBalance });
if(web3.utils.fromWei(ethBalance.toString(), "ether") > 0 &&
if (
web3.utils.fromWei(ethBalance.toString(), 'ether') > 0 &&
Math.floor(utils.fromTokenDecimals(tokenBalance.toString(), decimals)) >= 1
){
) {
history.push('/poll/title');
return;
}
})
});
}
render(){
let ethBalance = web3.utils.fromWei(this.state.ethBalance, "ether");
let tokenBalance = this.state.tokenBalance != "-" ? Math.floor(utils.fromTokenDecimals(this.state.tokenBalance, this.props.decimals)) : "-";
render() {
let ethBalance = web3.utils.fromWei(this.state.ethBalance, 'ether');
let tokenBalance =
this.state.tokenBalance != '-'
? Math.floor(utils.fromTokenDecimals(this.state.tokenBalance, this.props.decimals))
: '-';
if(this.state.tokenBalance === "-") return null;
if (this.state.tokenBalance === '-') return null;
return <Fragment><div className="section">
<Typography variant="headline">Create a Poll</Typography>
<Card className="card credits" onClick={this.redirectToConnect}>
<CardContent>
<Typography component="div">
<span className="title">Voting Credits</span>
<span className="value">{tokenBalance}</span>
</Typography>
{ tokenBalance == 0 &&
<div className="warning">
<Typography component="h2">
No {this.props.symbol} in your wallet
</Typography>
<Typography component="p">
To create a poll, you need to connect with a wallet that holds {this.props.symbol} tokens.
</Typography>
</div>
}
</CardContent>
</Card>
{ ethBalance == 0 && <Card className="card credits">
<CardContent onClick={this.redirectToConnect}>
<Typography component="div">
<span className="title">ETH</span>
<span className="value">{ethBalance}</span>
</Typography>
<div className="warning" >
<Typography component="h2">
Not enough ETH in your wallet
return (
<Fragment>
<div className="section">
<Typography variant="headline">Create a Poll</Typography>
<Card className="card credits" onClick={this.redirectToConnect}>
<CardContent>
<Typography component="div">
<span className="title">Voting Credits</span>
<span className="value">{tokenBalance}</span>
</Typography>
<Typography component="p">
You will sign a transaction to publish the poll. You need ETH to pay for gas (Ethereum network fee).
</Typography>
</div>
</CardContent>
</Card> }
</div>
<div className={(ethBalance == 0 || tokenBalance == 0) ? 'buttonNav back' : 'buttonNav'}>
{ (ethBalance == 0 || tokenBalance == 0) && <Link to={"/"}><Button variant="text">Back</Button></Link> }
</div>
<p className="helpLink">Need help? <a onClick={this.handleClickOpen}>Chat with us</a></p>
</Fragment>;
{tokenBalance == 0 && (
<div className="warning">
<Typography component="h2">No {this.props.symbol} in your wallet</Typography>
<Typography component="p">
To create a poll, you need to connect with a wallet that holds {this.props.symbol} tokens.
</Typography>
</div>
)}
</CardContent>
</Card>
{ethBalance == 0 && (
<Card className="card credits">
<CardContent onClick={this.redirectToConnect}>
<Typography component="div">
<span className="title">ETH</span>
<span className="value">{ethBalance}</span>
</Typography>
<div className="warning">
<Typography component="h2">Not enough ETH in your wallet</Typography>
<Typography component="p">
You will sign a transaction to publish the poll. You need ETH to pay for gas (Ethereum network fee).
</Typography>
</div>
</CardContent>
</Card>
)}
</div>
<div className={ethBalance == 0 || tokenBalance == 0 ? 'buttonNav back' : 'buttonNav'}>
{(ethBalance == 0 || tokenBalance == 0) && (
<Link to={'/'}>
<Button variant="text">Back</Button>
</Link>
)}
</div>
<p className="helpLink">
Need help? <a onClick={this.handleClickOpen}>Chat with us</a>
</p>
</Fragment>
);
}
}

View File

@ -1,160 +1,211 @@
import React, {Component, Fragment} from 'react';
import { withRouter } from 'react-router-dom'
import Typography from '@material-ui/core/Typography'
import {Link} from "react-router-dom";
import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom';
import Typography from '@material-ui/core/Typography';
import { Link } from 'react-router-dom';
function pad(number, length) {
var str = '' + number;
var str = String(number);
while (str.length < length) {
str = '0' + str;
str = '0' + str;
}
return str;
}
Date.prototype.DDMMYYYYatHHMM = function () {
var yyyy = this.getFullYear().toString();
var MM = pad(this.getMonth() + 1,2);
var dd = pad(this.getDate(), 2);
var hh = pad(this.getHours(), 2);
var mm = pad(this.getMinutes(), 2)
return dd + '/' + MM + '/' + yyyy + ' at ' + hh + ':' + mm + (this.getHours() > 12 ? 'pm' : 'am');
};
Date.prototype.DDMMYYYYatHHMM = function() {
var yyyy = this.getFullYear().toString();
var MM = pad(this.getMonth() + 1, 2);
var dd = pad(this.getDate(), 2);
var hh = pad(this.getHours(), 2);
var mm = pad(this.getMinutes(), 2);
return dd + '/' + MM + '/' + yyyy + ' at ' + hh + ':' + mm + (this.getHours() > 12 ? 'pm' : 'am');
};
class PollCreationResults extends Component {
state = {
poll: {},
idPoll: null,
isError: false,
isPending: true,
netId: 3
};
state = {
poll: {},
idPoll: null,
isError: false,
isPending: true,
netId: 3
componentDidMount() {
const { pollTransaction, pollTransactionHash, history } = this.props;
if (!pollTransaction || !pollTransactionHash) {
history.push('/');
return;
}
componentDidMount(){
const {pollTransaction, pollTransactionHash, history} = this.props;
this.setState({ poll: Object.assign({}, this.props.poll) });
if(!pollTransaction || !pollTransactionHash){
history.push('/');
return;
}
EmbarkJS.onReady(() => {
web3.eth.net.getId((err, netId) => {
this.setState({ netId });
});
this.setState({poll: Object.assign({}, this.props.poll)});
EmbarkJS.onReady(() => {
web3.eth.net.getId((err, netId) => {
this.setState({netId});
});
if(pollTransaction){
pollTransaction.catch(x => {
this.setState({isError: true});
});
let req = false;
let interval = setInterval(async () => {
if(req || !pollTransactionHash) return;
req = true;
const receipt = await web3.eth.getTransactionReceipt(pollTransactionHash);
if(receipt){
clearInterval(interval);
if(receipt.status || receipt.status == "0x1"){
this.setState({isPending: false});
const ev = web3.eth.abi.decodeLog(PollManager.options.jsonInterface.find(x => x.name == "PollCreated").inputs, receipt.logs[0].data, receipt.logs[0].topics.slice(1));
await this.props.getPolls();
const poll = await PollManager.methods.poll(ev.idPoll).call();
this.props.loadPollContent(poll);
this.props.resetPoll();
this.setState({idPoll: ev.idPoll})
} else {
this.setState({isError: true});
}
}
req = false;
}, 100);
}
if (pollTransaction) {
pollTransaction.catch(error => {
this.setState({ isError: true });
});
}
linkTo = id => e => {
let url = window.location.href.replace(window.location.hash, '');
url += '?d=' + (new Date()).getTime() + '#/titleScreen/' + id;
let req = false;
let interval = setInterval(async () => {
if (req || !pollTransactionHash) return;
window.location.href = url;
}
req = true;
const receipt = await web3.eth.getTransactionReceipt(pollTransactionHash);
if (receipt) {
clearInterval(interval);
render(){
const {pollTransaction, pollTransactionHash} = this.props;
let {isError, isPending, netId, idPoll, poll} = this.state;
if (receipt.status || receipt.status == '0x1') {
this.setState({ isPending: false });
const etherscanURL = netId == 3 ? 'https://ropsten.etherscan.io/tx/' : ( netId == 1 ? "https://etherscan.io/tx/" : '');
const ev = web3.eth.abi.decodeLog(
PollManager.options.jsonInterface.find(x => x.name == 'PollCreated').inputs,
receipt.logs[0].data,
receipt.logs[0].topics.slice(1)
);
if(poll.options == null) return null;
await this.props.getPolls();
const poll = await PollManager.methods.poll(ev.idPoll).call();
this.props.loadPollContent(poll);
return <Fragment>
{ isError && <div className="errorTrx">
<div className="image"><img src="../images/sad-face.svg" width="24" /></div>
<Typography variant="headline">Transaction failed</Typography>
<Typography variant="body1">Your transaction failed to be written to the blockchain. This is usually because of network congestion. Please try again</Typography>
<Link to={"/poll/review/"}>
<Button color="primary" variant="contained">Try again</Button>
</Link>
</div> }
this.props.resetPoll();
{ !isError && pollTransaction && <div className="transactionArea">
{ isPending && <div className="pending">
<div className="spinner">
<div className="bounce1"></div>
<div className="bounce2"></div>
<div className="bounce3"></div>
</div>
<Typography variant="headline">Your poll will be created once the transaction is complete</Typography>
<Typography variant="body1">Your poll is in the process of being confirmed in the blockchain</Typography>
</div>
}
{ !isPending && pollTransaction && <div className="confirmed">
<img src="images/confirmed.svg" width="40" />
<Typography variant="headline">The poll was created and its now live!</Typography>
</div>}
{ pollTransactionHash && etherscanURL && <Typography variant="body1"><a target="_blank" href={ etherscanURL + pollTransactionHash}>View details on Etherscan</a></Typography> }
{ idPoll !== null && <Typography variant="body1"><a href="#" onClick={this.linkTo(idPoll)}>See the poll</a></Typography> }
</div>
}
<div className="section">
<div className="reviewDetails">
<Typography variant="h3">{poll.title}<br /><br /></Typography>
<Typography variant="body1">{poll.description}<br /><br /><br /></Typography>
<div className="pollOption ">
<Typography variant="h3" className="grayHeader">Poll starts:</Typography>
<Typography variant="body2" className="detail">Today (upon publishing)<br /><br /></Typography>
<Typography variant="h3" className="grayHeader">Poll ends:</Typography>
<Typography variant="body2" className="detail">{poll.endDate.DDMMYYYYatHHMM()}</Typography>
</div>
<Typography variant="h3" className="grayHeader"><br /><br />Options</Typography>
{
poll.options.map((item, i) => {
return <div className="pollOption" key={i}>
<Typography variant="display1">{item.title}</Typography>
<Typography variant="body2">{item.content}</Typography>
</div>
})
this.setState({ idPoll: ev.idPoll });
} else {
this.setState({ isError: true });
}
}
</div>
</div>
</Fragment>;
req = false;
}, 100);
}
});
}
linkTo = id => e => {
let url = window.location.href.replace(window.location.hash, '');
url += '?d=' + new Date().getTime() + '#/titleScreen/' + id;
window.location.href = url;
};
render() {
const { pollTransaction, pollTransactionHash } = this.props;
let { isError, isPending, netId, idPoll, poll } = this.state;
const etherscanURL = netId == 3 ? 'https://ropsten.etherscan.io/tx/' : netId == 1 ? 'https://etherscan.io/tx/' : '';
if (poll.options == null) return null;
return (
<Fragment>
{isError && (
<div className="errorTrx">
<div className="image">
<img src="../images/sad-face.svg" width="24" />
</div>
<Typography variant="headline">Transaction failed</Typography>
<Typography variant="body1">
Your transaction failed to be written to the blockchain. This is usually because of network congestion.
Please try again
</Typography>
<Link to={'/poll/review/'}>
<Button color="primary" variant="contained">
Try again
</Button>
</Link>
</div>
)}
{!isError && pollTransaction && (
<div className="transactionArea">
{isPending && (
<div className="pending">
<div className="spinner">
<div className="bounce1" />
<div className="bounce2" />
<div className="bounce3" />
</div>
<Typography variant="headline">Your poll will be created once the transaction is complete</Typography>
<Typography variant="body1">
Your poll is in the process of being confirmed in the blockchain
</Typography>
</div>
)}
{!isPending && pollTransaction && (
<div className="confirmed">
<img src="images/confirmed.svg" width="40" />
<Typography variant="headline">The poll was created and its now live!</Typography>
</div>
)}
{pollTransactionHash && etherscanURL && (
<Typography variant="body1">
<a target="_blank" href={etherscanURL + pollTransactionHash}>
View details on Etherscan
</a>
</Typography>
)}
{idPoll !== null && (
<Typography variant="body1">
<a href="#" onClick={this.linkTo(idPoll)}>
See the poll
</a>
</Typography>
)}
</div>
)}
<div className="section">
<div className="reviewDetails">
<Typography variant="h3">
{poll.title}
<br />
<br />
</Typography>
<Typography variant="body1">
{poll.description}
<br />
<br />
<br />
</Typography>
<div className="pollOption ">
<Typography variant="h3" className="grayHeader">
Poll starts:
</Typography>
<Typography variant="body2" className="detail">
Today (upon publishing)
<br />
<br />
</Typography>
<Typography variant="h3" className="grayHeader">
Poll ends:
</Typography>
<Typography variant="body2" className="detail">
{poll.endDate.DDMMYYYYatHHMM()}
</Typography>
</div>
<Typography variant="h3" className="grayHeader">
<br />
<br />
Options
</Typography>
{poll.options.map((item, i) => {
return (
<div className="pollOption" key={i}>
<Typography variant="display1">{item.title}</Typography>
<Typography variant="body2">{item.content}</Typography>
</div>
);
})}
</div>
</div>
</Fragment>
);
}
}
export default withRouter(PollCreationResults);

View File

@ -1,80 +1,82 @@
import React, {Component, Fragment} from 'react';
import { withRouter } from 'react-router-dom'
import Typography from '@material-ui/core/Typography'
import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom';
import Typography from '@material-ui/core/Typography';
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import FormHelperText from '@material-ui/core/FormHelperText';
import LinearProgress from '@material-ui/core/LinearProgress';
class PollDescription extends Component {
state = {
description: '',
error: ''
};
state = {
description: '',
error: ''
componentDidMount() {
if (this.props.poll.description !== undefined) {
this.setState({ description: this.props.poll.description });
}
componentDidMount(){
if(this.props.poll.description !== undefined){
this.setState({description: this.props.poll.description});
}
if(!this.props.poll.title){
const {history} = this.props;
history.push('/');
}
if (!this.props.poll.title) {
const { history } = this.props;
history.push('/');
}
}
handleChange = (event) => {
this.setState({error: ''});
if(event.target.value.length <= 500){
this.props.assignToPoll({description: event.target.value});
this.setState({description: event.target.value});
}
handleChange = event => {
this.setState({ error: '' });
if (event.target.value.length <= 500) {
this.props.assignToPoll({ description: event.target.value });
this.setState({ description: event.target.value });
}
};
continue = () => {
const {description} = this.state;
const {history} = this.props;
continue = () => {
const { description } = this.state;
const { history } = this.props;
if(description.trim() != ''){
this.props.assignToPoll({description});
history.push('/poll/options');
} else {
this.setState({error: "Required"})
}
if (description.trim() != '') {
this.props.assignToPoll({ description });
history.push('/poll/options');
} else {
this.setState({ error: 'Required' });
}
};
render() {
return <Fragment>
render() {
return (
<Fragment>
<LinearProgress variant="determinate" value={38} />
<div className="section pollCreation">
<Typography variant="headline">Create a Poll</Typography>
<div style={{marginRight:"24px", marginTop:"8px"}}>
<Typography variant="headline">Create a Poll</Typography>
<div style={{ marginRight: '24px', marginTop: '8px' }}>
<TextField
id="standard-multiline-flexible"
label="Poll description"
multiline
error={this.state.error != ''}
fullWidth
autoFocus
className="inputTxt"
margin="normal"
InputLabelProps={{
shrink: true,
}}
value={this.state.description}
onChange={this.handleChange}
id="standard-multiline-flexible"
label="Poll description"
multiline
error={this.state.error != ''}
fullWidth
autoFocus
className="inputTxt"
margin="normal"
InputLabelProps={{
shrink: true
}}
value={this.state.description}
onChange={this.handleChange}
/>
</div>
{this.state.error && <FormHelperText className="errorText">{this.state.error}</FormHelperText>}
<small>{this.state.description.length} of 500</small>
</div>
{this.state.error && <FormHelperText className="errorText">{this.state.error}</FormHelperText>}
<small>{this.state.description.length} of 500</small>
</div>
<div className="buttonNav">
<Button onClick={this.continue} disabled={this.state.description.trim().length == 0}>Next</Button>
<Button onClick={this.continue} disabled={this.state.description.trim().length == 0}>
Next
</Button>
</div>
</Fragment>;
}
</Fragment>
);
}
}
export default withRouter(PollDescription);

View File

@ -1,6 +1,6 @@
import React, {Component, Fragment} from 'react';
import { withRouter } from 'react-router-dom'
import Typography from '@material-ui/core/Typography'
import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom';
import Typography from '@material-ui/core/Typography';
import TextField from '@material-ui/core/TextField';
import FormHelperText from '@material-ui/core/FormHelperText';
import Button from '@material-ui/core/Button';
@ -8,7 +8,7 @@ import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import {SortableContainer, SortableElement, arrayMove} from 'react-sortable-hoc';
import { SortableContainer, SortableElement, arrayMove } from 'react-sortable-hoc';
import LinearProgress from '@material-ui/core/LinearProgress';
import Slide from '@material-ui/core/Slide';
@ -16,14 +16,14 @@ function Transition(props) {
return <Slide direction="up" {...props} />;
}
const SortableItem = SortableElement(({value, editOption}) =>
<div className="pollOption" onDoubleClick={editOption}>
<Typography variant="display1">{value.title}</Typography>
<Typography variant="body2">{value.content}</Typography>
</div>
);
const SortableItem = SortableElement(({ value, editOption }) => (
<div className="pollOption" onDoubleClick={editOption}>
<Typography variant="display1">{value.title}</Typography>
<Typography variant="body2">{value.content}</Typography>
</div>
));
const SortableList = SortableContainer(({items, removeOption, editOption}) => {
const SortableList = SortableContainer(({ items, removeOption, editOption }) => {
return (
<div>
{items.map((value, index) => (
@ -34,167 +34,200 @@ const SortableList = SortableContainer(({items, removeOption, editOption}) => {
});
class PollOptions extends Component {
state = {
error: '',
open: false,
options: [],
optionTitle: '',
optionContent: '',
edit: null,
stickyBarClass: 'stickyBar'
};
state = {
error: '',
onSortEnd = ({ oldIndex, newIndex }) => {
this.setState({
options: arrayMove(this.state.options, oldIndex, newIndex)
});
};
handleChange = name => event => {
this.setState({ error: '' });
const state = {};
state[name] = event.target.value;
this.setState(state);
};
handleClickOpen = () => {
this.setState({ open: true, stickyBarClass: '' });
};
addOption = () => {
if (this.state.optionTitle.trim() == '') {
this.setState({ error: 'Required' });
} else {
const options = this.state.options;
if (this.state.edit !== null) {
options[this.state.edit] = { title: this.state.optionTitle, content: this.state.optionContent };
} else {
options.push({ title: this.state.optionTitle, content: this.state.optionContent });
}
this.props.assignToPoll({ options });
this.setState({
open: false,
options: [],
edit: null,
optionTitle: '',
optionContent: '',
edit: null,
error: '',
stickyBarClass: 'stickyBar'
});
}
};
handleClose = () => {
this.setState({ open: false, edit: null, optionContent: '', optionTitle: '', stickyBarClass: 'stickyBar' });
};
removeOption = i => () => {
if (i != null) {
if (confirm('Are you sure you want to delete this option?')) {
this.state.options.splice(i, 1);
this.setState({ options: this.state.options });
this.handleClose();
}
}
};
editOption = i => () => {
const state = {};
state.open = true;
state.optionContent = this.state.options[i].content;
state.optionTitle = this.state.options[i].title;
state.edit = i;
state.stickyBarClass = '';
this.setState(state);
};
continue = () => {
const { options } = this.state;
const { history } = this.props;
if (options.length != 0) {
this.props.assignToPoll({ options });
history.push('/poll/schedule');
} else {
this.setState({ error: 'Required' });
}
};
componentDidMount() {
if (this.props.poll.options !== undefined) {
this.setState({ options: this.props.poll.options });
}
onSortEnd = ({oldIndex, newIndex}) => {
this.setState({
options: arrayMove(this.state.options, oldIndex, newIndex),
});
};
handleChange = name => event => {
this.setState({error: ''});
const state = {};
state[name] = event.target.value;
this.setState(state);
if (!this.props.poll.description) {
const { history } = this.props;
history.push('/');
}
}
handleClickOpen = () => {
this.setState({ open: true, stickyBarClass: '' });
};
addOption = () => {
if(this.state.optionTitle.trim() == ''){
this.setState({error: 'Required'});
} else {
const options = this.state.options;
if(this.state.edit !== null){
options[this.state.edit] = {title: this.state.optionTitle, content: this.state.optionContent};
} else {
options.push({title: this.state.optionTitle, content: this.state.optionContent});
}
this.props.assignToPoll({options});
this.setState({ open: false, edit: null, optionTitle: '', optionContent: '', error: '', stickyBarClass: 'stickyBar' });
}
}
handleClose = () => {
this.setState({ open: false, edit: null, optionContent: '', optionTitle: '', stickyBarClass: 'stickyBar' });
}
removeOption = i => () => {
if(i != null){
if(confirm("Are you sure you want to delete this option?")){
this.state.options.splice(i, 1);
this.setState({options: this.state.options});
this.handleClose();
}
}
}
editOption = i => () => {
const state = {};
state.open = true;
state.optionContent = this.state.options[i].content;
state.optionTitle = this.state.options[i].title;
state.edit = i;
state.stickyBarClass = '';
this.setState(state);
}
continue = () => {
const {options} = this.state;
const {history} = this.props;
if(options.length != 0){
this.props.assignToPoll({options});
history.push('/poll/schedule');
} else {
this.setState({error: "Required"})
}
}
componentDidMount(){
if(this.props.poll.options !== undefined){
this.setState({options: this.props.poll.options});
}
if(!this.props.poll.description){
const {history} = this.props;
history.push('/');
}
}
render() {
return <Fragment>
render() {
return (
<Fragment>
<LinearProgress variant="determinate" value={57} />
<div className="section pollCreation">
<div className={this.state.stickyBarClass}>
<div className={this.state.stickyBarClass}>
<Typography variant="headline">Create a Poll</Typography>
<Typography variant="body1" style={{marginTop: '20px'}}>Add options to the poll</Typography>
<a onClick={this.handleClickOpen} className="addOption"><img src="images/plus-button.svg" width="40" />Add option</a>
</div>
<SortableList lockAxis={"y"} pressDelay={200} items={this.state.options} editOption={this.editOption} removeOption={this.removeOption} onSortEnd={this.onSortEnd} />
<Typography variant="body1" style={{ marginTop: '20px' }}>
Add options to the poll
</Typography>
<a onClick={this.handleClickOpen} className="addOption">
<img src="images/plus-button.svg" width="40" />
Add option
</a>
</div>
<SortableList
lockAxis={'y'}
pressDelay={200}
items={this.state.options}
editOption={this.editOption}
removeOption={this.removeOption}
onSortEnd={this.onSortEnd}
/>
</div>
<div className="buttonNav">
<Button onClick={this.continue} disabled={this.state.options.length == 0}>Next</Button>
<Button onClick={this.continue} disabled={this.state.options.length == 0}>
Next
</Button>
</div>
<Dialog
fullScreen={true}
open={this.state.open}
onClose={this.handleClose}
className="pollCreation"
TransitionComponent={Transition}
fullScreen={true}
open={this.state.open}
onClose={this.handleClose}
className="pollCreation"
TransitionComponent={Transition}
>
<DialogActions>
<Button onClick={this.handleClose} color="primary" style={{position:"absolute", left: "0px", align:"left"}}>
<img src="images/x-close.svg" />
<DialogActions>
<Button
onClick={this.handleClose}
color="primary"
style={{ position: 'absolute', left: '0px', align: 'left' }}
>
<img src="images/x-close.svg" />
</Button>
{ this.state.edit !== null && <Button onClick={this.removeOption(this.state.edit)}>
{this.state.edit !== null && (
<Button onClick={this.removeOption(this.state.edit)}>
<img src="images/trash-icon.svg" />
</Button> }
<Button onClick={this.addOption} color="primary" style={{fontSize:"15px", lineHeight:"22px"}} disabled={this.state.optionTitle.trim().length == 0 || this.state.optionContent.length == 0}>
Save
</Button>
)}
<Button
onClick={this.addOption}
color="primary"
style={{ fontSize: '15px', lineHeight: '22px' }}
disabled={this.state.optionTitle.trim().length == 0 || this.state.optionContent.length == 0}
>
Save
</Button>
</DialogActions>
<DialogTitle id="responsive-dialog-title">{ this.state.edit !== null ? "Edit poll option" : "Add new poll option" }</DialogTitle>
<DialogContent className="optionDialog">
<TextField
label="Option title"
autoFocus
error={this.state.error != ''}
className="inputTxt"
fullWidth
margin="normal"
value={this.state.optionTitle}
onChange={this.handleChange('optionTitle')}
InputLabelProps={{
shrink: true,
}}
/>
{this.state.error && <FormHelperText className="errorText">{this.state.error}</FormHelperText>}
<div style={{marginRight:"24px"}}>
<TextField
id="standard-multiline-flexible"
label="Option description"
multiline
fullWidth
className="inputTxt"
margin="normal"
value={this.state.optionContent}
onChange={this.handleChange('optionContent')}
InputLabelProps={{
shrink: true,
}}
/>
</div>
</DialogContent>
</DialogActions>
<DialogTitle id="responsive-dialog-title">
{this.state.edit !== null ? 'Edit poll option' : 'Add new poll option'}
</DialogTitle>
<DialogContent className="optionDialog">
<TextField
label="Option title"
autoFocus
error={this.state.error != ''}
className="inputTxt"
fullWidth
margin="normal"
value={this.state.optionTitle}
onChange={this.handleChange('optionTitle')}
InputLabelProps={{
shrink: true
}}
/>
{this.state.error && <FormHelperText className="errorText">{this.state.error}</FormHelperText>}
<div style={{ marginRight: '24px' }}>
<TextField
id="standard-multiline-flexible"
label="Option description"
multiline
fullWidth
className="inputTxt"
margin="normal"
value={this.state.optionContent}
onChange={this.handleChange('optionContent')}
InputLabelProps={{
shrink: true
}}
/>
</div>
</DialogContent>
</Dialog>
</Fragment>;
}
</Fragment>
);
}
}
export default (withRouter(PollOptions));
export default withRouter(PollOptions);

View File

@ -1,110 +1,131 @@
import React, {Component, Fragment} from 'react';
import { withRouter } from 'react-router-dom'
import Typography from '@material-ui/core/Typography'
import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import LinearProgress from '@material-ui/core/LinearProgress';
function pad(number, length) {
var str = '' + number;
while (str.length < length) {
str = '0' + str;
}
return str;
var str = String(number);
while (str.length < length) {
str = '0' + str;
}
Date.prototype.DDMMYYYYatHHMM = function () {
var yyyy = this.getFullYear().toString();
var MM = pad(this.getMonth() + 1,2);
var dd = pad(this.getDate(), 2);
var hh = pad(this.getHours(), 2);
var mm = pad(this.getMinutes(), 2)
return dd + '/' + MM + '/' + yyyy + ' at ' + hh + ':' + mm + (this.getHours() > 12 ? 'pm' : 'am');
};
return str;
}
Date.prototype.DDMMYYYYatHHMM = function() {
var yyyy = this.getFullYear().toString();
var MM = pad(this.getMonth() + 1, 2);
var dd = pad(this.getDate(), 2);
var hh = pad(this.getHours(), 2);
var mm = pad(this.getMinutes(), 2);
return dd + '/' + MM + '/' + yyyy + ' at ' + hh + ':' + mm + (this.getHours() > 12 ? 'pm' : 'am');
};
class PollReview extends Component {
state = {
isSubmitting: false
};
state = {
isSubmitting : false
componentDidMount() {
if (this.props.poll.endDate === null || this.props.poll.endDate === undefined) {
const { history } = this.props;
history.push('/');
}
}
componentDidMount(){
if(this.props.poll.endDate === null || this.props.poll.endDate === undefined){
const {history} = this.props;
history.push('/');
}
}
sign = async () => {
this.setState({ isSubmitting: true });
sign = async () => {
this.setState({isSubmitting: true});
const {history, poll} = this.props;
const addPollOnlyEndTime = PollManager.methods["addPoll(uint256,bytes,uint8)"];
const { history, poll } = this.props;
const addPollOnlyEndTime = PollManager.methods['addPoll(uint256,bytes,uint8)'];
const endTime = parseInt(poll.endDate.getTime() / 1000, 10);
const pollObj = {
'title': poll.title,
'description': poll.description,
'ballots': poll.options
}
const pollObjString = JSON.stringify(pollObj);
const endTime = parseInt(poll.endDate.getTime() / 1000, 10);
const pollObj = {
title: poll.title,
description: poll.description,
ballots: poll.options
};
const pollObjString = JSON.stringify(pollObj);
const ipfsHash = await EmbarkJS.Storage.saveText(pollObjString);
const encodedDesc = web3.utils.toHex(ipfsHash);
const toSend = addPollOnlyEndTime(endTime, encodedDesc, pollObj.ballots.length || 0);
const gasEstimated = await toSend.estimateGas({from: web3.eth.defaultAccount});
const transaction = toSend.send({gas: gasEstimated + 100000, from: web3.eth.defaultAccount});
const ipfsHash = await EmbarkJS.Storage.saveText(pollObjString);
const encodedDesc = web3.utils.toHex(ipfsHash);
const toSend = addPollOnlyEndTime(endTime, encodedDesc, pollObj.ballots.length || 0);
const gasEstimated = await toSend.estimateGas({ from: web3.eth.defaultAccount });
const transaction = toSend.send({ gas: gasEstimated + 100000, from: web3.eth.defaultAccount });
transaction.on('transactionHash', hash => {
this.props.setPollTransactionHash(hash);
this.props.setPollTransactionPromise(transaction);
history.push('/poll/results/');
});
transaction.on('transactionHash', hash => {
this.props.setPollTransactionHash(hash);
this.props.setPollTransactionPromise(transaction);
history.push('/poll/results/');
});
transaction.catch(err => {
this.setState({isSubmitting: false});
});
}
transaction.catch(error => {
this.setState({ isSubmitting: false });
});
};
render() {
const {poll} = this.props;
render() {
const { poll } = this.props;
if(!poll.options) return null;
if (!poll.options) return null;
return <Fragment>
<LinearProgress variant={this.state.isSubmitting ? "indeterminate" : "determinate"} value={this.state.isSubmitting ? 100 : 96} />
return (
<Fragment>
<LinearProgress
variant={this.state.isSubmitting ? 'indeterminate' : 'determinate'}
value={this.state.isSubmitting ? 100 : 96}
/>
<div className="section pollCreation">
<Typography variant="headline">Review details</Typography>
<Typography variant="headline">Review details</Typography>
<div className="reviewDetails">
<Typography variant="h3">{poll.title}<br /><br /></Typography>
<Typography variant="body1">{poll.description}</Typography>
<div className="pollOption" style={{height:"144px", marginTop:"32px", border:"1px solid #EEF2F5"}}>
<Typography variant="h3" className="grayHeader">Poll starts:</Typography>
<Typography variant="body2" className="detail">Today (upon publishing)<br /><br /></Typography>
<Typography variant="h3" className="grayHeader">Poll ends:</Typography>
<Typography variant="body2" className="detail">{poll.endDate.DDMMYYYYatHHMM()}</Typography>
</div>
<Typography variant="h3" className="grayHeader" style={{marginBottom:"8px"}}><br /><br />Options</Typography>
{
poll.options.map((item, i) => {
return <div className="pollOption" style={{padding:"16px", height:"72px"}} key={i}>
<Typography variant="display1">{item.title}</Typography>
<Typography variant="body2">{item.content}</Typography>
</div>
})
}
<div className="reviewDetails">
<Typography variant="h3">
{poll.title}
<br />
<br />
</Typography>
<Typography variant="body1">{poll.description}</Typography>
<div className="pollOption" style={{ height: '144px', marginTop: '32px', border: '1px solid #EEF2F5' }}>
<Typography variant="h3" className="grayHeader">
Poll starts:
</Typography>
<Typography variant="body2" className="detail">
Today (upon publishing)
<br />
<br />
</Typography>
<Typography variant="h3" className="grayHeader">
Poll ends:
</Typography>
<Typography variant="body2" className="detail">
{poll.endDate.DDMMYYYYatHHMM()}
</Typography>
</div>
<Typography variant="h3" className="grayHeader" style={{ marginBottom: '8px' }}>
<br />
<br />
Options
</Typography>
{poll.options.map((item, i) => {
return (
<div className="pollOption" style={{ padding: '16px', height: '72px' }} key={i}>
<Typography variant="display1">{item.title}</Typography>
<Typography variant="body2">{item.content}</Typography>
</div>
);
})}
</div>
</div>
<div className="buttonNav">
<Button onClick={this.sign} disabled={this.state.isSubmitting}>Sign to confirm</Button>
<Button onClick={this.sign} disabled={this.state.isSubmitting}>
Sign to confirm
</Button>
</div>
</Fragment>;
}
</Fragment>
);
}
}
export default withRouter(PollReview);

View File

@ -1,14 +1,11 @@
import React, {Component, Fragment} from 'react';
import { withRouter } from 'react-router-dom'
import Typography from '@material-ui/core/Typography'
import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom';
import Typography from '@material-ui/core/Typography';
import PropTypes from 'prop-types';
import Button from '@material-ui/core/Button';
import LinearProgress from '@material-ui/core/LinearProgress';
import { InlineDateTimePicker } from 'material-ui-pickers';
import InfiniteCalendar, {
Calendar,
withRange,
} from 'react-infinite-calendar';
import InfiniteCalendar, { Calendar, withRange } from 'react-infinite-calendar';
import 'react-infinite-calendar/styles.css';
import { setHours, setMinutes, getHours, getMinutes } from 'date-fns';
import { monthNames, timeValues, timeGroups } from '../../standard/constants';
@ -17,187 +14,188 @@ import TimePickerDialog from './TimePickerDialog';
const CalendarWithRange = withRange(Calendar);
Date.prototype.addDays = function(days) {
var date = new Date(this.valueOf());
date.setDate(date.getDate() + days);
return date;
}
var date = new Date(this.valueOf());
date.setDate(date.getDate() + days);
return date;
};
class PollSchedule extends Component {
static propTypes = {
poll: PropTypes.object,
assignToPoll: PropTypes.func
};
static propTypes = {
poll: PropTypes.object,
assignToPoll: PropTypes.func,
state = {
endDate: false,
error: '',
timeDialog: false,
timeValues: {
hour: '00',
minute: '00',
ampm: 'AM'
}
};
componentDidMount() {
const { endDate, options } = this.props.poll;
if (endDate !== undefined) {
const minute = `${getMinutes(endDate)}`;
const hour = `${getHours(endDate)}`;
const ampm = hour > 12 ? 'PM' : 'AM';
const timeValues = { hour, minute, ampm };
this.setState({ endDate: endDate, timeValues });
} else {
const currentDate = new Date();
const hrs = getHours(currentDate);
const mins = getMinutes(currentDate);
const minute = mins < 10 ? '0' + mins : `${mins}`;
let hour = hrs % 12;
hour = hour ? `${hour}` : 12;
hour = hour < 10 ? '0' + hour : `${hour}`;
const ampm = hrs >= 12 ? 'PM' : 'AM';
const timeValues = { hour, minute, ampm };
this.setState({ endDate: new Date().addDays(15), timeValues });
}
state = {
endDate: false,
error: '',
timeDialog: false,
timeValues: {
hour: '00',
minute: '00',
ampm: 'AM'
if (!options) {
const { history } = this.props;
history.push('/');
}
}
handleDateChange = date => {
this.props.assignToPoll({ endDate: date.end });
this.setState({ endDate: date.end });
};
handleTimeChange = (name, value) => {
const { endDate } = this.state;
this.setState(({ timeValues }) => ({
timeValues: {
...timeValues,
[name]: value
}
}));
if (name == 'hour') {
const updatedHour = setHours(endDate, value);
this.setState({ endDate: updatedHour });
} else if (name == 'minute') {
const updatedMinute = setMinutes(endDate, value);
this.setState({ endDate: updatedMinute });
} else {
let newHour;
if (value == 'AM') {
if (getHours(endDate) >= 12) {
newHour = getHours(endDate) - 12;
const updatedHour = setHours(endDate, newHour);
this.setState({ endDate: updatedHour });
}
}
componentDidMount(){
const { endDate, options } = this.props.poll;
if(endDate !== undefined){
const minute = `${getMinutes(endDate)}`
const hour = `${getHours(endDate)}`
const ampm = hour > 12 ? 'PM' : 'AM'
const timeValues = { hour, minute, ampm }
this.setState({endDate: endDate, timeValues});
} else {
const currentDate = new Date();
const hrs = getHours(currentDate)
const mins = getMinutes(currentDate)
const minute = mins < 10 ? '0' + mins : `${mins}`
let hour = hrs % 12
hour = hour ? `${hour}` : 12
hour = hour < 10 ? '0' + hour: `${hour}`
const ampm = hrs >= 12 ? 'PM' : 'AM'
const timeValues = { hour, minute, ampm }
this.setState({endDate: (new Date()).addDays(15), timeValues });
}
if(!options){
const {history} = this.props;
history.push('/');
} else {
if (getHours(endDate) < 12) {
newHour = getHours(endDate) + 12;
const updatedHour = setHours(endDate, newHour);
this.setState({ endDate: updatedHour });
}
}
}
};
handleDateChange = date => {
this.props.assignToPoll({endDate: date.end});
this.setState({ endDate: date.end });
continue = () => {
const { endDate } = this.state;
const { history } = this.props;
if (endDate != null) {
this.props.assignToPoll({ endDate: endDate });
history.push('/poll/review');
} else {
this.setState({ error: 'Required' });
}
};
handleTimeChange = (name, value) => {
const { endDate } = this.state;
this.setState(({timeValues}) => ({
timeValues: {
...timeValues,
[name]: value
}
}));
if(name == 'hour') {
const updatedHour = setHours(endDate, value)
this.setState({ endDate: updatedHour})
} else if(name == 'minute') {
const updatedMinute = setMinutes(endDate, value)
this.setState({ endDate: updatedMinute})
} else {
let newHour;
if(value == 'AM') {
if(getHours(endDate) >= 12) {
newHour = getHours(endDate) - 12
const updatedHour = setHours(endDate, newHour)
this.setState({ endDate: updatedHour })
}
} else {
if(getHours(endDate) < 12) {
newHour = getHours(endDate) + 12
const updatedHour = setHours(endDate, newHour)
this.setState({ endDate: updatedHour })
}
}
}
}
formatAMPM = date => {
let hours = date.getHours();
let minutes = date.getMinutes();
let ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
hours = hours || 12; // the hour '0' should be '12'
minutes = minutes < 10 ? '0' + minutes : minutes;
let strTime = hours + ':' + minutes + ' ' + ampm;
return strTime;
};
continue = () => {
const {endDate} = this.state;
const {history} = this.props;
if(endDate != null){
this.props.assignToPoll({endDate: endDate});
history.push('/poll/review');
} else {
this.setState({error: "Required"})
}
}
toggleTimeDialog = prevState => {
this.setState({
timeDialog: !prevState.timeDialog
});
};
formatAMPM = (date) => {
let hours = date.getHours();
let minutes = date.getMinutes();
let ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
hours = hours ? hours : 12; // the hour '0' should be '12'
minutes = minutes < 10 ? '0'+minutes : minutes;
let strTime = hours + ':' + minutes + ' ' + ampm;
return strTime;
}
closeTimeDialog = () => {
this.setState({
timeDialog: false
});
};
toggleTimeDialog = prevState => {
this.setState({
timeDialog: !prevState.timeDialog
});
};
render() {
const { endDate, timeDialog, timeValues } = this.state;
const today = new Date();
closeTimeDialog = () => {
this.setState({
timeDialog: false
});
};
render() {
const { endDate, timeDialog, timeValues } = this.state;
const today = new Date();
return <Fragment>
return (
<Fragment>
<LinearProgress variant="determinate" value={77} id="p" />
<div className="section pollCreation">
<Typography variant="headline">Create a Poll</Typography>
<Typography variant="body1" style={{marginTop: '20px'}}>Set the end date and time for the poll.</Typography>
<div>
<InfiniteCalendar
height={200}
className="schedule-calendar"
Component={CalendarWithRange}
min={today}
minDate={today}
onSelect={this.handleDateChange}
selected={{
start: today,
end: endDate
}}
theme={{
selectionColor: '#4360DF',
accentColor: '#4360DF',
todayColor: '#4360DF',
}}
displayOptions={{
showHeader: false,
showTodayHelper: false,
}}
locale={{
headerFormat: 'MMM Do',
}}
/>
</div>
<Typography variant="headline">Create a Poll</Typography>
<Typography variant="body1" style={{ marginTop: '20px' }}>
Set the end date and time for the poll.
</Typography>
<div>
<InfiniteCalendar
height={200}
className="schedule-calendar"
Component={CalendarWithRange}
min={today}
minDate={today}
onSelect={this.handleDateChange}
selected={{
start: today,
end: endDate
}}
theme={{
selectionColor: '#4360DF',
accentColor: '#4360DF',
todayColor: '#4360DF'
}}
displayOptions={{
showHeader: false,
showTodayHelper: false
}}
locale={{
headerFormat: 'MMM Do'
}}
/>
</div>
</div>
<div className="buttonNav scheduleNav">
<div className="end-time-container">
<div className="endDateTime">
Ends:
<div className="endDate">
{ endDate && `${monthNames[endDate.getMonth()]} ${endDate.getDate()} ` }
</div>
<div className="endTime">
{ endDate && `at ${this.formatAMPM(endDate)}`}
</div>
</div>
<div className="editTimeText" onClick={this.toggleTimeDialog}>Edit Time</div>
<TimePickerDialog timeDialog={timeDialog} timeValues={timeValues} handleTimeChange={this.handleTimeChange} closeTimeDialog={this.closeTimeDialog} />
<div className="end-time-container">
<div className="endDateTime">
Ends:
<div className="endDate">{endDate && `${monthNames[endDate.getMonth()]} ${endDate.getDate()} `}</div>
<div className="endTime">{endDate && `at ${this.formatAMPM(endDate)}`}</div>
</div>
<Button onClick={this.continue}>Review</Button>
<div className="editTimeText" onClick={this.toggleTimeDialog}>
Edit Time
</div>
<TimePickerDialog
timeDialog={timeDialog}
timeValues={timeValues}
handleTimeChange={this.handleTimeChange}
closeTimeDialog={this.closeTimeDialog}
/>
</div>
<Button onClick={this.continue}>Review</Button>
</div>
</Fragment>;
}
</Fragment>
);
}
}
export default withRouter(PollSchedule);

View File

@ -1,82 +1,84 @@
import React, {Component, Fragment} from 'react';
import { withRouter } from 'react-router-dom'
import Typography from '@material-ui/core/Typography'
import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom';
import Typography from '@material-ui/core/Typography';
import TextField from '@material-ui/core/TextField';
import FormHelperText from '@material-ui/core/FormHelperText';
import LinearProgress from '@material-ui/core/LinearProgress';
import Button from '@material-ui/core/Button';
class PollTitle extends Component {
state = {
title: '',
error: ''
};
state = {
title: '',
error: ''
componentDidMount() {
if (!web3.eth.defaultAccount) {
this.props.history.push('/');
}
componentDidMount(){
if(!web3.eth.defaultAccount){
this.props.history.push('/');
}
if(this.props.poll.title !== undefined){
this.setState({title: this.props.poll.title});
} else {
this.props.assignToPoll({title: ''});
}
if (this.props.poll.title !== undefined) {
this.setState({ title: this.props.poll.title });
} else {
this.props.assignToPoll({ title: '' });
}
}
handleChange = (event) => {
this.setState({error: ''});
if(event.target.value.length <= 70){
this.props.assignToPoll({title: event.target.value});
this.setState({title: event.target.value});
}
handleChange = event => {
this.setState({ error: '' });
if (event.target.value.length <= 70) {
this.props.assignToPoll({ title: event.target.value });
this.setState({ title: event.target.value });
}
};
continue = () => {
const {title} = this.state;
const {history} = this.props;
continue = () => {
const { title } = this.state;
const { history } = this.props;
if(title.trim() != ''){
this.props.assignToPoll({title});
history.push('/poll/description');
} else {
this.setState({error: "Required"})
}
if (title.trim() != '') {
this.props.assignToPoll({ title });
history.push('/poll/description');
} else {
this.setState({ error: 'Required' });
}
};
render() {
return <Fragment>
render() {
return (
<Fragment>
<LinearProgress variant="determinate" value={19} />
<div className="section pollCreation">
<Typography variant="headline">Create a Poll</Typography>
<div style={{marginRight:"24px", marginTop:"8px"}}>
<Typography variant="headline">Create a Poll</Typography>
<div style={{ marginRight: '24px', marginTop: '8px' }}>
<TextField
label="Poll name"
multiline
autoFocus={true}
error={this.state.error != ''}
className="inputTxt"
placeholder="E.g. What is the best ice cream flavor?"
rowsMax="3"
fullWidth
value={this.state.title}
onChange={this.handleChange}
margin="normal"
InputLabelProps={{
shrink: true,
}}
label="Poll name"
multiline
autoFocus={true}
error={this.state.error != ''}
className="inputTxt"
placeholder="E.g. What is the best ice cream flavor?"
rowsMax="3"
fullWidth
value={this.state.title}
onChange={this.handleChange}
margin="normal"
InputLabelProps={{
shrink: true
}}
/>
</div>
{this.state.error && <FormHelperText className="errorText">{this.state.error}</FormHelperText>}
<small>{this.state.title.length} of 70</small>
</div>
{this.state.error && <FormHelperText className="errorText">{this.state.error}</FormHelperText>}
<small>{this.state.title.length} of 70</small>
</div>
<div className="buttonNav">
<Button onClick={this.continue} disabled={this.state.title.trim().length == 0}>Next</Button>
<Button onClick={this.continue} disabled={this.state.title.trim().length == 0}>
Next
</Button>
</div>
</Fragment>;
}
</Fragment>
);
}
}
export default withRouter(PollTitle);

View File

@ -1,30 +1,28 @@
import React, { Component } from 'react'
import React, { Component } from 'react';
import Picker from 'react-mobile-picker';
import { timeGroups } from '../../standard/constants';
import Dialog from '@material-ui/core/Dialog';
import Button from '@material-ui/core/Button';
const dialogStyle = {
width: '80vh',
width: '80vh'
};
export default class TimePickerDialog extends Component {
render() {
const { timeDialog, handleTimeChange, closeTimeDialog, timeValues } = this.props;
return (
<Dialog onClose={closeTimeDialog} PaperProps={{style: dialogStyle}} open={timeDialog} >
<Dialog onClose={closeTimeDialog} PaperProps={{ style: dialogStyle }} open={timeDialog}>
<div className="time-picker-title">Set end time</div>
<div>
<Picker
optionGroups={timeGroups}
valueGroups={timeValues}
height="160"
onChange={handleTimeChange} />
<div className="time-picker-save" >
<a className="landingPageButton" onClick={closeTimeDialog}>SAVE</a>
<Picker optionGroups={timeGroups} valueGroups={timeValues} height="160" onChange={handleTimeChange} />
<div className="time-picker-save">
<a className="landingPageButton" onClick={closeTimeDialog}>
SAVE
</a>
</div>
</div>
</Dialog>
)
);
}
}

View File

@ -1,9 +1,9 @@
import {Link} from "react-router-dom";
import { Link } from 'react-router-dom';
import Button from '@material-ui/core/Button';
import React, {Component} from 'react';
import Typography from '@material-ui/core/Typography'
import DappToken from 'Embark/contracts/DappToken';
import { withRouter } from 'react-router-dom'
import React, { Component } from 'react';
import Typography from '@material-ui/core/Typography';
import DappToken from 'Embark/contracts/DappToken';
import { withRouter } from 'react-router-dom';
import PollManager from 'Embark/contracts/PollManager';
import utils from '../../../utils/utils';
@ -11,56 +11,66 @@ class ConnectYourWallet extends Component {
connectWallet = async () => {
// TODO: extract this to utils, this code is repeated here, in other wallets and in How voting works
const {history, polls, updateBalances, idPoll, decimals} = this.props;
const { history, polls, updateBalances, idPoll, decimals } = this.props;
const poll = polls[idPoll];
let cont = true;
if (window.ethereum) {
try {
await ethereum.enable();
web3.setProvider(ethereum);
const accounts = await web3.eth.getAccounts();
web3.eth.defaultAccount = accounts[0];
await ethereum.enable();
web3.setProvider(ethereum);
const accounts = await web3.eth.getAccounts();
web3.eth.defaultAccount = accounts[0];
} catch (error) {
cont = false;
}
}
if(cont){
const tknVotes = await PollManager.methods.getVote(idPoll, web3.eth.defaultAccount).call({from: web3.eth.defaultAccount});
if (cont) {
const tknVotes = await PollManager.methods
.getVote(idPoll, web3.eth.defaultAccount)
.call({ from: web3.eth.defaultAccount });
const votes = tknVotes.map(x => Math.sqrt(parseInt(utils.fromTokenDecimals(x, decimals))));
if(web3.currentProvider.isStatus){
if(idPoll == 'poll-creation'){
if (web3.currentProvider.isStatus) {
if (idPoll == 'poll-creation') {
history.push('/poll/create');
} else {
const tokenBalance = await DappToken.methods.balanceOfAt(web3.eth.defaultAccount, poll._startBlock).call({from: web3.eth.defaultAccount});
const tokenBalance = await DappToken.methods
.balanceOfAt(web3.eth.defaultAccount, poll._startBlock)
.call({ from: web3.eth.defaultAccount });
const ethBalance = await web3.eth.getBalance(web3.eth.defaultAccount);
updateBalances(idPoll, tokenBalance, ethBalance, votes);
history.push('/votingCredits/' + idPoll);
}
} else {
window.location.href = "https://get.status.im/browse/" + location.href.replace(/^http(s?):\/\//, '');
window.location.href = 'https://get.status.im/browse/' + location.href.replace(/^http(s?):\/\//, '');
}
}
}
};
render(){
const {idPoll, symbol} = this.props;
render() {
const { idPoll, symbol } = this.props;
return <div className="section center">
<Typography variant="headline">Connect your wallet</Typography>
<Typography variant="body1">To start voting, connect to a wallet where you hold your {symbol} assets.</Typography>
<div className="action">
<Button color="primary" onClick={this.connectWallet} variant="contained">CONNECT USING STATUS</Button>
</div>
<div className="action">
<Link to={"/otherWallets/" + idPoll}>
<Button color="primary">CONNECT WITH ANOTHER WALLET</Button>
</Link>
</div>
</div>;
return (
<div className="section center">
<Typography variant="headline">Connect your wallet</Typography>
<Typography variant="body1">
To start voting, connect to a wallet where you hold your {symbol} assets.
</Typography>
<div className="action">
<Button color="primary" onClick={this.connectWallet} variant="contained">
CONNECT USING STATUS
</Button>
</div>
<div className="action">
<Link to={'/otherWallets/' + idPoll}>
<Button color="primary">CONNECT WITH ANOTHER WALLET</Button>
</Link>
</div>
</div>
);
}
}

View File

@ -1,59 +1,60 @@
import React, {Component} from 'react';
import { withRouter } from 'react-router-dom'
import DappToken from 'Embark/contracts/DappToken';
import PollManager from 'Embark/contracts/PollManager';
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import DappToken from 'Embark/contracts/DappToken';
import PollManager from 'Embark/contracts/PollManager';
import utils from '../../../utils/utils';
class ExternalWallet extends Component {
componentDidUpdate(prevProps){
if (this.props.polls !== prevProps.polls && this.props.polls) {
this.connectWallet();
}
componentDidUpdate(prevProps) {
if (this.props.polls !== prevProps.polls && this.props.polls) {
this.connectWallet();
}
}
componentDidMount() {
this.connectWallet();
}
connectWallet = async () => {
const { history, polls, updateBalances, idPoll, decimals } = this.props;
if (!polls) return;
const poll = polls.find(p => p.idPoll == idPoll);
if (!poll) return null;
let cont = true;
if (window.ethereum) {
try {
await ethereum.enable();
web3.setProvider(ethereum);
const accounts = await web3.eth.getAccounts();
web3.eth.defaultAccount = accounts[0];
} catch (error) {
cont = false;
}
}
componentDidMount(){
this.connectWallet();
if (cont) {
// TODO: extract this code to utils. It's repeated in ConnectYourWallt, ExternalWallet and HowVotingWorks
const tknVotes = await PollManager.methods
.getVote(idPoll, web3.eth.defaultAccount)
.call({ from: web3.eth.defaultAccount });
// TODO: use decimals
const votes = tknVotes.map(x => Math.sqrt(parseInt(utils.fromTokenDecimals(x, decimals))));
const tokenBalance = await DappToken.methods
.balanceOfAt(web3.eth.defaultAccount, poll._startBlock)
.call({ from: web3.eth.defaultAccount });
const ethBalance = await web3.eth.getBalance(web3.eth.defaultAccount);
updateBalances(idPoll, tokenBalance, ethBalance, votes);
history.push('/votingCredits/' + idPoll);
}
};
connectWallet = async () => {
const {history, polls, updateBalances, idPoll, decimals} = this.props;
if(!polls) return;
const poll = polls.find(p => p.idPoll == idPoll);
if(!poll) return null;
let cont = true;
if (window.ethereum) {
try {
await ethereum.enable();
web3.setProvider(ethereum);
const accounts = await web3.eth.getAccounts();
web3.eth.defaultAccount = accounts[0];
} catch (error) {
cont = false;
}
}
if(cont){
// TODO: extract this code to utils. It's repeated in ConnectYourWallt, ExternalWallet and HowVotingWorks
const tknVotes = await PollManager.methods.getVote(idPoll, web3.eth.defaultAccount).call({from: web3.eth.defaultAccount});
// TODO: use decimals
const votes = tknVotes.map(x => Math.sqrt(parseInt( utils.fromTokenDecimals(x, decimals))));
const tokenBalance = await DappToken.methods.balanceOfAt(web3.eth.defaultAccount, poll._startBlock).call({from: web3.eth.defaultAccount});
const ethBalance = await web3.eth.getBalance(web3.eth.defaultAccount);
updateBalances(idPoll, tokenBalance, ethBalance, votes);
history.push('/votingCredits/' + idPoll);
}
};
render(){
return null;
}
render() {
return null;
}
}
export default withRouter(ExternalWallet);
export default withRouter(ExternalWallet);

View File

@ -1,20 +1,20 @@
import {Link} from "react-router-dom";
import { Link } from 'react-router-dom';
import Button from '@material-ui/core/Button';
import React, {Component, Fragment} from 'react';
import React, { Component, Fragment } from 'react';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import Typography from '@material-ui/core/Typography';
import HelpDialog from '../HelpDialog';
// TODO: extract to utils
Date.prototype.DDMMYYYYatHHMM = function () {
Date.prototype.DDMMYYYYatHHMM = function() {
var yyyy = this.getFullYear().toString();
var MM = pad(this.getMonth() + 1,2);
var MM = pad(this.getMonth() + 1, 2);
var dd = pad(this.getDate(), 2);
var hh = pad(this.getHours(), 2);
var mm = pad(this.getMinutes(), 2)
var mm = pad(this.getMinutes(), 2);
return dd + '/' + MM + '/' + yyyy + ' at ' + hh + ':' + mm + (this.getHours() > 12 ? 'pm' : 'am');
return dd + '/' + MM + '/' + yyyy + ' at ' + hh + ':' + mm + (this.getHours() > 12 ? 'pm' : 'am');
};
function getDate() {
@ -23,19 +23,17 @@ function getDate() {
}
function pad(number, length) {
var str = '' + number;
var str = String(number);
while (str.length < length) {
str = '0' + str;
str = '0' + str;
}
return str;
}
class OtherWallets extends Component {
state = {
open: false,
open: false
};
handleClickOpen = () => {
@ -48,69 +46,78 @@ class OtherWallets extends Component {
render() {
const props = this.props;
let poll = null;
let d = new Date();
if(!props.noWeb3Provider){
if(!props.polls){
if (!props.noWeb3Provider) {
if (!props.polls) {
return null;
}
if(props.idPoll != "poll-creation"){
if (props.idPoll != 'poll-creation') {
poll = props.polls.find(p => p.idPoll == props.idPoll);
if(!poll) return null;
if (!poll) return null;
d = new Date(poll.blockInfo.timestamp * 1000);
}
}
}
return <Fragment><div className="section">
<Typography variant="headline">Connect with a wallet with {this.props.symbol} in it.</Typography>
{ !props.noWeb3Provider && poll &&
<Typography variant="body1" className="pollTime">Poll creation date: <b>{d.DDMMYYYYatHHMM()}</b></Typography>
}
<Typography variant="body1">Using your desktop computer</Typography>
<Card className="card">
<CardContent>
<Typography gutterBottom component="h2">
MetaMask
</Typography>
<Typography component="p">
If you keep your {this.props.symbol} in MetaMask, please open vote.status.im in Google Chrome and make sure you are connected to the account where you keep your {this.props.symbol}.
</Typography>
</CardContent>
</Card>
<Card className="card">
<CardContent>
<Typography gutterBottom component="h2">
Ledger or Trezor
</Typography>
<Typography component="p">
If you keep your {this.props.symbol} in a Ledger or Trezor, please connect the device to MetaMask. Then open vote.status.im in Google Chrome with your hardware wallets account selected in metamask.
</Typography>
</CardContent>
</Card>
<Card className="card">
<CardContent>
<Typography gutterBottom component="h2">
{this.props.symbol} on Exchanges
</Typography>
<Typography component="p">
We are sorry. {this.props.symbol} held on exchanges dont qualify for voting.
To vote in the next poll, move your {this.props.symbol} to a wallet where you control the private keys.
</Typography>
</CardContent>
</Card>
<p className="helpLink">Need help? <a onClick={this.handleClickOpen}>Chat with us</a></p>
</div>
<div className="buttonNav back">
<Link to={props.idPoll !== undefined ? "/wallet/" + props.idPoll : '/'}><Button variant="text">Back</Button></Link>
</div>
<HelpDialog open={this.state.open} symbol={this.props.symbol} handleClose={this.handleClose} />
</Fragment>
return (
<Fragment>
<div className="section">
<Typography variant="headline">Connect with a wallet with {this.props.symbol} in it.</Typography>
{!props.noWeb3Provider && poll && (
<Typography variant="body1" className="pollTime">
Poll creation date: <b>{d.DDMMYYYYatHHMM()}</b>
</Typography>
)}
<Typography variant="body1">Using your desktop computer</Typography>
<Card className="card">
<CardContent>
<Typography gutterBottom component="h2">
MetaMask
</Typography>
<Typography component="p">
If you keep your {this.props.symbol} in MetaMask, please open vote.status.im in Google Chrome and make
sure you are connected to the account where you keep your {this.props.symbol}.
</Typography>
</CardContent>
</Card>
<Card className="card">
<CardContent>
<Typography gutterBottom component="h2">
Ledger or Trezor
</Typography>
<Typography component="p">
If you keep your {this.props.symbol} in a Ledger or Trezor, please connect the device to MetaMask. Then
open vote.status.im in Google Chrome with your hardware wallets account selected in metamask.
</Typography>
</CardContent>
</Card>
<Card className="card">
<CardContent>
<Typography gutterBottom component="h2">
{this.props.symbol} on Exchanges
</Typography>
<Typography component="p">
We are sorry. {this.props.symbol} held on exchanges dont qualify for voting. To vote in the next poll,
move your {this.props.symbol} to a wallet where you control the private keys.
</Typography>
</CardContent>
</Card>
<p className="helpLink">
Need help? <a onClick={this.handleClickOpen}>Chat with us</a>
</p>
</div>
<div className="buttonNav back">
<Link to={props.idPoll !== undefined ? '/wallet/' + props.idPoll : '/'}>
<Button variant="text">Back</Button>
</Link>
</div>
<HelpDialog open={this.state.open} symbol={this.props.symbol} handleClose={this.handleClose} />
</Fragment>
);
}
}
export default OtherWallets;
export default OtherWallets;

View File

@ -1,28 +1,37 @@
import React, {Component} from 'react';
import React, { Component } from 'react';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography'
import Typography from '@material-ui/core/Typography';
import { withRouter } from 'react-router-dom';
import { HashRouter as Router, Route, Link, Switch } from "react-router-dom";
import { HashRouter as Router, Route, Link, Switch } from 'react-router-dom';
const NoConnection = () => {
return (
<Route path="/" render={() => {
return <div className="section center">
<Typography variant="headline">Status SNT Voting</Typography>
<Typography variant="body1">To start voting, connect to a wallet where you hold your SNT</Typography>
<div className="action">
<a href="https://get.status.im/browse/vote.status.im"><Button color="primary" variant="contained">CONNECT USING STATUS</Button></a>
</div>
<div className="action">
<Link to={"/connectOtherWallet"}>
<Button color="primary">USE ANOTHER WALLET</Button>
</Link>
</div>
</div>;
}} />
<Route
path="/"
render={() => {
return (
<div className="section center">
<Typography variant="headline">Status SNT Voting</Typography>
<Typography variant="body1">To start voting, connect to a wallet where you hold your SNT</Typography>
<div className="action">
<a href="https://get.status.im/browse/vote.status.im">
<Button color="primary" variant="contained">
CONNECT USING STATUS
</Button>
</a>
</div>
<div className="action">
<Link to={'/connectOtherWallet'}>
<Button color="primary">USE ANOTHER WALLET</Button>
</Link>
</div>
</div>
);
}}
/>
);
};
NoConnection.displayName = "NoConnection";
NoConnection.displayName = 'NoConnection';
export default withRouter(NoConnection)
export default withRouter(NoConnection);

View File

@ -1,15 +1,17 @@
import React, { Fragment } from 'react';
import NoConnection from './NoConnection';
const NoWeb3 = () => (
<div>
NO WEB3 Provider detected
</div>
)
const NoWeb3 = () => <div>NO WEB3 Provider detected</div>;
const Web3Render = ({ ready, children }) => (
<Fragment>
{ready ? <Fragment>{children}</Fragment> : <div id="votingDapp"><NoConnection /></div> }
{ready ? (
<Fragment>{children}</Fragment>
) : (
<div id="votingDapp">
<NoConnection />
</div>
)}
</Fragment>
);

View File

@ -1,13 +1,71 @@
// Month names array for calendar
export const monthNames = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
];
export const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// Time dicktionary for picker
export const timeGroups = {
hour: ['00','01','02','03','04','05','06','07','08','09','10','11','12'],
minute: ['00','01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50','51','52','53','54','55','56','57','58','59','60'],
hour: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'],
minute: [
'00',
'01',
'02',
'03',
'04',
'05',
'06',
'07',
'08',
'09',
'10',
'11',
'12',
'13',
'14',
'15',
'16',
'17',
'18',
'19',
'20',
'21',
'22',
'23',
'24',
'25',
'26',
'27',
'28',
'29',
'30',
'31',
'32',
'33',
'34',
'35',
'36',
'37',
'38',
'39',
'40',
'41',
'42',
'43',
'44',
'45',
'46',
'47',
'48',
'49',
'50',
'51',
'52',
'53',
'54',
'55',
'56',
'57',
'58',
'59',
'60'
],
ampm: ['AM', 'PM']
};

View File

@ -1,9 +1,9 @@
export default {
NoEthereumSection: {
maxWidth: '980px',
margin: '0 auto',
textAlign: 'center',
fontSize: '24px'
maxWidth: '980px',
margin: '0 auto',
textAlign: 'center',
fontSize: '24px'
},
ImgHeaderLogo: {
width: '264px',
@ -15,7 +15,7 @@ export default {
marginBottom: '40px'
},
PNotFound: {
opacity: 0.50,
opacity: 0.5,
width: '925px',
display: 'inline-block',
marginTop: '50px',
@ -66,6 +66,4 @@ export default {
borderRadius: '4px',
margin: '0 20px'
}
}
};

View File

@ -3,7 +3,7 @@ import BigNumber from 'bignumber.js';
// By default BigNumber's `toString` method converts to exponential notation if the value has
// more then 20 digits. We want to avoid this behavior, so we set EXPONENTIAL_AT to a high number
BigNumber.config({
EXPONENTIAL_AT: 1000,
EXPONENTIAL_AT: 1000
});
export { BigNumber };

View File

@ -1,5 +1,5 @@
import React from 'react';
import web3 from "Embark/web3"
import web3 from 'Embark/web3';
import EmbarkJS from 'Embark/EmbarkJS';
import DappToken from 'Embark/contracts/DappToken';
import FormControl from '@material-ui/core/FormControl';
@ -8,7 +8,6 @@ import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import { withStyles } from '@material-ui/core/styles';
const styles = () => ({
mlb: {
marginLeft: '1rem',
@ -22,114 +21,117 @@ const styles = () => ({
});
class TokenUI extends React.Component {
constructor(props) {
super(props);
this.state = {
address: "",
amountToMint: "100000000000000000000",
accountBalance: 0,
accountB: web3.eth.defaultAccount,
balanceOf: 0,
logs: []
}
}
componentDidMount(){
EmbarkJS.onReady(async () => {
this.setState({address: web3.eth.defaultAccount});
});
}
handleMintAmountChange(e){
this.setState({amountToMint: e.target.value});
}
mint(e){
e.preventDefault();
var value = parseInt(this.state.amountToMint, 10);
var address = this.state.address;
DappToken.methods.controller().call()
.then((controller) => {
return DappToken.methods.generateTokens(address, value.toString())
.send({from: controller, gasLimit: 1000000});
})
.then(console.log);
this._addToLog(DappToken.options.address +".mint("+value+").send({from: " + web3.eth.defaultAccount + "})");
}
getBalance(e){
e.preventDefault();
if (EmbarkJS.isNewWeb3()) {
DappToken.methods.balanceOf(web3.eth.defaultAccount).call()
.then(_value => this.setState({accountBalance: _value}))
} else {
DappToken.balanceOf(web3.eth.defaultAccount)
.then(_value => this.x({valueGet: _value}))
}
this._addToLog(DappToken.options.address + ".balanceOf(" + web3.eth.defaultAccount + ")");
}
_addToLog(txt){
this.state.logs.push(txt);
this.setState({logs: this.state.logs});
}
render(){
const { classes } = this.props;
return (<React.Fragment>
<h3> 1. Mint your token</h3>
<FormControl>
<FormGroup>
<TextField
label="Address"
value={this.state.address}
className={classes.mlb}
onChange={(e) => this.setState({address: e.target.value}) }
variant="outlined"
/>
<TextField
label="Amount to Mint"
defaultValue={this.state.amountToMint}
className={classes.mlb}
onChange={(e) => this.handleMintAmountChange(e)}
variant="outlined"
/>
<Button variant="contained" onClick={(e) => this.mint(e)} className={classes.adminButton}>
Mint
</Button>
</FormGroup>
</FormControl>
<h3> 2. Read your account token balance </h3>
<FormControl>
<FormGroup>
<div>
You text token balance is <span className="accountBalance">{this.state.accountBalance}</span>
</div>
<Button className={classes.adminButton} variant="contained" onClick={(e) => this.getBalance(e)}>
Get Balance
</Button>
</FormGroup>
</FormControl>
<h3> 3. Contract Calls </h3>
<p>Javascript calls being made: </p>
<div className="logs">
{
this.state.logs.map((item, i) => <p key={i}>{item}</p>)
}
</div>
</React.Fragment>
);
}
constructor(props) {
super(props);
this.state = {
address: '',
amountToMint: '100000000000000000000',
accountBalance: 0,
accountB: web3.eth.defaultAccount,
balanceOf: 0,
logs: []
};
}
componentDidMount() {
EmbarkJS.onReady(async () => {
this.setState({ address: web3.eth.defaultAccount });
});
}
handleMintAmountChange(e) {
this.setState({ amountToMint: e.target.value });
}
mint(e) {
e.preventDefault();
var value = parseInt(this.state.amountToMint, 10);
var address = this.state.address;
DappToken.methods
.controller()
.call()
.then(controller => {
return DappToken.methods
.generateTokens(address, value.toString())
.send({ from: controller, gasLimit: 1000000 });
})
.then(console.log);
this._addToLog(DappToken.options.address + '.mint(' + value + ').send({from: ' + web3.eth.defaultAccount + '})');
}
getBalance(e) {
e.preventDefault();
if (EmbarkJS.isNewWeb3()) {
DappToken.methods
.balanceOf(web3.eth.defaultAccount)
.call()
.then(_value => this.setState({ accountBalance: _value }));
} else {
DappToken.balanceOf(web3.eth.defaultAccount).then(_value => this.x({ valueGet: _value }));
}
this._addToLog(DappToken.options.address + '.balanceOf(' + web3.eth.defaultAccount + ')');
}
_addToLog(txt) {
this.state.logs.push(txt);
this.setState({ logs: this.state.logs });
}
render() {
const { classes } = this.props;
return (
<React.Fragment>
<h3> 1. Mint your token</h3>
<FormControl>
<FormGroup>
<TextField
label="Address"
value={this.state.address}
className={classes.mlb}
onChange={e => this.setState({ address: e.target.value })}
variant="outlined"
/>
<TextField
label="Amount to Mint"
defaultValue={this.state.amountToMint}
className={classes.mlb}
onChange={e => this.handleMintAmountChange(e)}
variant="outlined"
/>
<Button variant="contained" onClick={e => this.mint(e)} className={classes.adminButton}>
Mint
</Button>
</FormGroup>
</FormControl>
<h3> 2. Read your account token balance </h3>
<FormControl>
<FormGroup>
<div>
You text token balance is <span className="accountBalance">{this.state.accountBalance}</span>
</div>
<Button className={classes.adminButton} variant="contained" onClick={e => this.getBalance(e)}>
Get Balance
</Button>
</FormGroup>
</FormControl>
<h3> 3. Contract Calls </h3>
<p>Javascript calls being made: </p>
<div className="logs">
{this.state.logs.map((item, i) => (
<p key={i}>{item}</p>
))}
</div>
</React.Fragment>
);
}
}
export default withStyles(styles)(TokenUI);

View File

@ -1,14 +1,14 @@
import React, { Fragment } from 'react';
import ReactDOM from 'react-dom';
import web3 from "Embark/web3";
import web3 from 'Embark/web3';
import EmbarkJS from 'Embark/EmbarkJS';
import PollManager from 'Embark/contracts/PollManager';
import Voting from './components/Voting';
import DappToken from 'Embark/contracts/DappToken';
import DappToken from 'Embark/contracts/DappToken';
import { VotingContext } from './context';
import Web3Render from './components/standard/Web3Render';
import { getPolls, omitPolls } from './utils/polls';
import { HashRouter as Router, Route, Switch } from "react-router-dom";
import { HashRouter as Router, Route, Switch } from 'react-router-dom';
import OtherWallets from './components/flow/wallet/OtherWallets';
import Typography from '@material-ui/core/Typography';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
@ -23,11 +23,11 @@ const muiTheme = createMuiTheme({
root: {
height: '44px',
fontSize: '15px',
lineHeight: '22px',
lineHeight: '22px'
},
label: {
fontSize: '15px',
lineHeight: '22px',
lineHeight: '22px'
}
}
}
@ -40,7 +40,7 @@ const TESTNET = 3;
// #38
setTimeout(() => {
if(!(window.web3 || window.ethereum)){
if (!(window.web3 || window.ethereum)) {
window.location.reload(true);
}
}, 5000);
@ -48,84 +48,102 @@ setTimeout(() => {
const pollsPerLoad = 3;
class App extends React.Component {
state = {
admin: false,
pollOrder: 'NEWEST_ADDED',
web3Provider: true,
loading: true,
name: '----',
symbol: '',
decimals: '18',
networkName: '',
rawPolls: [],
pollsRequested: [],
start: 0,
end: pollsPerLoad
};
state = { admin: false, pollOrder: 'NEWEST_ADDED', web3Provider: true, loading: true, name: '----', symbol: "", decimals: "18", networkName: "" , rawPolls: [], pollsRequested: [], start: 0,
end: pollsPerLoad};
componentDidMount(){
EmbarkJS.onReady((err) => {
componentDidMount() {
EmbarkJS.onReady(err => {
if (err) this.setState({ web3Provider: false });
else {
if(!web3.eth.defaultAccount){
web3.eth.defaultAccount = "0x0000000000000000000000000000000000000000";
if (!web3.eth.defaultAccount) {
web3.eth.defaultAccount = '0x0000000000000000000000000000000000000000';
}
DappToken.methods.symbol().call({from: web3.eth.defaultAccount}).then(symbol => {
this.setState({symbol});
});
DappToken.methods.decimals().call({from: web3.eth.defaultAccount}).then(decimals => {
this.setState({decimals});
});
DappToken.methods
.symbol()
.call({ from: web3.eth.defaultAccount })
.then(symbol => {
this.setState({ symbol });
});
DappToken.methods.name().call({from: web3.eth.defaultAccount}).then(name => {
this.setState({name});
})
DappToken.methods
.decimals()
.call({ from: web3.eth.defaultAccount })
.then(decimals => {
this.setState({ decimals });
});
DappToken.methods
.name()
.call({ from: web3.eth.defaultAccount })
.then(name => {
this.setState({ name });
});
this._getPolls();
}
web3.eth.net.getId((err, netId) => {
if(EmbarkJS.environment === 'testnet' && netId !== TESTNET){
this.setState({web3Provider: false, networkName: "Ropsten"});
} else if(EmbarkJS.environment === 'livenet' && netId !== MAINNET){
this.setState({web3Provider: false, networkName: "Mainnet"});
if (EmbarkJS.environment === 'testnet' && netId !== TESTNET) {
this.setState({ web3Provider: false, networkName: 'Ropsten' });
} else if (EmbarkJS.environment === 'livenet' && netId !== MAINNET) {
this.setState({ web3Provider: false, networkName: 'Mainnet' });
}
})
})
});
});
}
setAccount(_account){
this.setState({account: _account});
setAccount(_account) {
this.setState({ account: _account });
}
_loadIPFSContent = async (polls) => {
for(let i = 0; i < polls.length; i++){
_loadIPFSContent = async polls => {
for (let i = 0; i < polls.length; i++) {
try {
let ipfsContent = await EmbarkJS.Storage.get(web3.utils.toAscii(polls[i]._description));
polls[i].content = JSON.parse(ipfsContent);
} catch(err){
console.log(err);
} catch (error) {
console.log(error);
}
}
let oPolls = {};
for(let i = 0; i < polls.length; i++){
for (let i = 0; i < polls.length; i++) {
oPolls[polls[i].idPoll] = polls[i];
}
this.setState({ rawPolls: oPolls, loading: false });
}
};
_getPolls = async () => {
this.setState({ loading: true });
const { nPolls, poll } = PollManager.methods;
const polls = await nPolls().call({from: web3.eth.defaultAccount});
if (polls) getPolls(polls, poll)
.then(omitPolls)
.then(rawPolls => {
rawPolls = rawPolls.sort((a,b) => {
if(a.idPoll > b.idPoll) return -1;
if(a.idPoll < b.idPoll) return 1;
return 0;
});
this.setState({rawPolls, loading: false});
});
const polls = await nPolls().call({ from: web3.eth.defaultAccount });
if (polls)
getPolls(polls, poll)
.then(omitPolls)
.then(rawPolls => {
rawPolls = rawPolls.sort((a, b) => {
if (a.idPoll > b.idPoll) return -1;
if (a.idPoll < b.idPoll) return 1;
return 0;
});
this.setState({ rawPolls, loading: false });
});
else this.setState({ rawPolls: [], loading: false });
}
};
updatePoll = async (idPoll) => {
updatePoll = async idPoll => {
const { poll, nPolls } = PollManager.methods;
const { rawPolls } = this.state;
const npolls = await nPolls().call();
@ -135,134 +153,180 @@ class App extends React.Component {
const updatedPoll = await poll(idPoll).call();
newPolls[idPoll] = { ...updatedPoll };
this.setState({ rawPolls: newPolls });
}
};
appendToPoll = (idPoll, data) => {
const { rawPolls } = this.state;
const newPolls = [...rawPolls];
newPolls[idPoll] = { ...newPolls[idPoll], ...data };
this.setState({ rawPolls: newPolls });
}
};
setPollOrder = pollOrder => { this.setState({ pollOrder }); }
setPollOrder = pollOrder => {
this.setState({ pollOrder });
};
_renderStatus(title, available) {
let className = available ? 'pull-right status-online' : 'pull-right status-offline';
return <Fragment>
{title}
<span className={className}></span>
</Fragment>;
return (
<Fragment>
{title}
<span className={className} />
</Fragment>
);
}
replacePoll = (poll) => {
replacePoll = poll => {
let rawPolls = this.state.rawPolls;
for(let i = 0; i < rawPolls.length; i++){
if(rawPolls[i].idPoll === poll.idPoll){
for (let i = 0; i < rawPolls.length; i++) {
if (rawPolls[i].idPoll === poll.idPoll) {
rawPolls[i] = poll;
this.setState({rawPolls, t: new Date().getTime()});
this.setState({ rawPolls, t: new Date().getTime() });
break;
}
}
}
};
loadPollContent = async poll => {
if (!poll) return;
loadPollContent = async (poll) => {
if(!poll) return;
let pollsRequested = this.state.pollsRequested;
if(!pollsRequested.includes(poll.idPoll)) pollsRequested.push(poll.idPoll);
this.setState({pollsRequested});
if (!pollsRequested.includes(poll.idPoll)) pollsRequested.push(poll.idPoll);
this.setState({ pollsRequested });
let ipfsContent = await EmbarkJS.Storage.get(web3.utils.toAscii(poll._description));
poll.content = JSON.parse(ipfsContent);
this.replacePoll(poll);
}
};
loadMorePolls = async (filterFn) => {
loadMorePolls = async filterFn => {
let start = this.state.start + pollsPerLoad;
let end = this.state.end + pollsPerLoad;
this.setState({start, end});
this.setState({ start, end });
this.loadPollRange(filterFn, start, end);
}
};
resetPollCounter = () => {
this.setState({start: 0, end: pollsPerLoad});
}
this.setState({ start: 0, end: pollsPerLoad });
};
loadPollRange = async (filterFn, start, end) => {
let rawPolls = this.state.rawPolls;
let polls = rawPolls.filter(filterFn).slice(start, end);
if(!polls.length) return;
if (!polls.length) return;
let pollsRequested = this.state.pollsRequested;
if(!pollsRequested) return;
if (!pollsRequested) return;
for (let i = 0; i < polls.length; i++) {
if (pollsRequested.includes(polls[i].idPoll)) continue;
for(let i = 0; i < polls.length; i++){
if(pollsRequested.includes(polls[i].idPoll)) continue;
pollsRequested.push(polls[i].idPoll);
let ipfsContent = await EmbarkJS.Storage.get(web3.utils.toAscii(polls[i]._description));
polls[i].content = JSON.parse(ipfsContent);
for(let i = 0; i < rawPolls.length; i++){
for(let j = 0; j < polls.length; j++){
if(rawPolls[i].idPoll == polls[j].idPoll){
for (let i = 0; i < rawPolls.length; i++) {
for (let j = 0; j < polls.length; j++) {
if (rawPolls[i].idPoll == polls[j].idPoll) {
rawPolls[i] = polls[j];
break;
}
}
}
this.setState({rawPolls, pollsRequested});
this.setState({ rawPolls, pollsRequested });
}
}
};
render() {
let { web3Provider, networkName } = this.state;
const {
_getPolls,
updatePoll,
setPollOrder,
appendToPoll,
replacePoll,
loadPollContent,
resetPollCounter,
loadPollRange,
loadMorePolls
} = this;
const votingContext = {
getPolls: _getPolls,
updatePoll,
appendToPoll,
setPollOrder,
resetPollCounter,
replacePoll,
loadPollContent,
loadPollRange,
loadMorePolls,
...this.state
};
render(){
let {web3Provider, networkName} = this.state;
const { _getPolls, updatePoll, setPollOrder, appendToPoll, replacePoll, loadPollContent, resetPollCounter, loadPollRange, loadMorePolls } = this;
const votingContext = { getPolls: _getPolls, updatePoll, appendToPoll, setPollOrder, resetPollCounter, replacePoll, loadPollContent, loadPollRange, loadMorePolls, ...this.state };
if(web3Provider) return <MuiThemeProvider theme={muiTheme}>
<Router>
<Web3Render ready={web3Provider}>
<VotingContext.Provider value={votingContext}>
<Voting />
</VotingContext.Provider>
</Web3Render>
</Router>
</MuiThemeProvider>;
if(networkName) return <MuiThemeProvider theme={muiTheme}>
<div>
<Typography variant="body1" style={{marginTop: "40vh", textAlign:"center"}}><img src="images/warning.svg" width="24" /><br /><br />Please connect to {networkName} to continue.</Typography>
</div>
</MuiThemeProvider>;
return <MuiThemeProvider theme={muiTheme}>
if (web3Provider)
return (
<MuiThemeProvider theme={muiTheme}>
<Router>
<Web3Render ready={web3Provider}>
<VotingContext.Provider value={votingContext}>
<Voting />
</VotingContext.Provider>
</Web3Render>
</Router>
</MuiThemeProvider>
);
if (networkName)
return (
<MuiThemeProvider theme={muiTheme}>
<div>
<Typography variant="body1" style={{ marginTop: '40vh', textAlign: 'center' }}>
<img src="images/warning.svg" width="24" />
<br />
<br />
Please connect to {networkName} to continue.
</Typography>
</div>
</MuiThemeProvider>
);
return (
<MuiThemeProvider theme={muiTheme}>
<Router>
<Fragment>
<Switch>
<Route exact path="/" render={() => {
return <Web3Render ready={web3Provider}>
<VotingContext.Provider value={votingContext}>
<Voting />
</VotingContext.Provider>
</Web3Render>;
}
} />
<Route path="/connectOtherWallet" render={() => <div id="votingDapp"><OtherWallets noWeb3Provider={true} /></div>} />
<Route
exact
path="/"
render={() => {
return (
<Web3Render ready={web3Provider}>
<VotingContext.Provider value={votingContext}>
<Voting />
</VotingContext.Provider>
</Web3Render>
);
}}
/>
<Route
path="/connectOtherWallet"
render={() => (
<div id="votingDapp">
<OtherWallets noWeb3Provider={true} />
</div>
)}
/>
</Switch>
</Fragment>
</Router>
</MuiThemeProvider>;
</MuiThemeProvider>
);
}
}
ReactDOM.render(<App></App>, document.getElementById('app'));
ReactDOM.render(<App />, document.querySelector('#app'));

View File

@ -3,4 +3,3 @@ import EmbarkJS from 'Embark/EmbarkJS';
// import your contracts
// e.g if you have a contract named SimpleStorage:
//import SimpleStorage from 'Embark/contracts/SimpleStorage';

View File

@ -1,10 +1,16 @@
import React from "react"
import React from 'react';
const Status = props => (
<svg width="330" height="124" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fillRule="evenodd">
<path d="M72.458 61.429c-7.431.427-12.088-1.299-19.52-.871a31.245 31.245 0 0 0-5.47.796C48.565 47.65 58.292 35.662 71.519 34.9c8.117-.467 16.23 4.53 16.67 12.642.433 7.973-5.664 13.307-15.73 13.886M52.503 89.46c-7.776.438-15.547-4.24-15.969-11.831-.415-7.462 5.427-12.454 15.07-12.996 7.118-.4 11.58 1.216 18.698.815a30.589 30.589 0 0 0 5.24-.745C74.493 77.528 65.175 88.748 52.503 89.46M62 .181C27.758.18 0 27.857 0 62s27.758 61.82 62 61.82c34.242 0 62-27.678 62-61.82C124 27.858 96.242.18 62 .18" fill="#4360DF" />
<path d="M152 71.11h9.464c0 1.389.452 2.386 1.356 2.991.905.605 2.066.908 3.484.908 1.172 0 2.132-.198 2.883-.596.75-.398 1.125-1.05 1.125-1.958 0-.536-.16-.976-.478-1.318-.319-.342-.807-.648-1.465-.916a34.317 34.317 0 0 0-2.517-.888l-3.71-1.18a61.345 61.345 0 0 1-3.653-1.304c-1.14-.45-2.142-1.012-3.005-1.683a7.086 7.086 0 0 1-2.02-2.453c-.483-.964-.724-2.159-.724-3.584 0-1.636.319-3.05.955-4.246a8.143 8.143 0 0 1 2.683-2.956c1.15-.776 2.528-1.358 4.13-1.745 1.604-.388 3.371-.582 5.303-.582 2.364 0 4.398.272 6.104.817 1.705.546 3.103 1.29 4.193 2.233 1.088.944 1.895 2.055 2.42 3.334.524 1.279.786 2.652.786 4.12h-9.526c0-1.28-.324-2.275-.972-2.988-.647-.713-1.628-1.07-2.943-1.07-1.007 0-1.87.226-2.59.677-.72.45-1.079 1.127-1.079 2.028 0 .605.17 1.09.51 1.454.34.364.839.693 1.497.984.66.292 1.472.595 2.44.907l3.326 1.076c1.355.356 2.619.75 3.79 1.185 1.17.435 2.203.964 3.096 1.586a6.936 6.936 0 0 1 2.127 2.34c.524.938.786 2.129.786 3.57 0 1.903-.37 3.523-1.11 4.861a9.342 9.342 0 0 1-2.975 3.267c-1.243.84-2.687 1.443-4.331 1.808-1.644.365-3.37.547-5.179.547-4.5 0-7.99-.959-10.466-2.877-2.477-1.918-3.715-4.702-3.715-8.35m28.768-12.938V50.86h4.7v-9.195h9.554v9.195h6.837v7.312h-6.837v11.015c0 1.144.102 2.054.305 2.73.203.676.493 1.207.87 1.591a2.64 2.64 0 0 0 1.374.749 8.744 8.744 0 0 0 1.816.171c.406 0 .926-.015 1.557-.047.63-.03 1.18-.088 1.648-.172v7.62a36.13 36.13 0 0 1-2.671.349c-1.068.106-2.325.159-3.77.159a26.9 26.9 0 0 1-3.548-.25 8.077 8.077 0 0 1-3.46-1.32c-1.062-.71-1.94-1.804-2.633-3.278-.695-1.475-1.042-3.5-1.042-6.073V58.171h-4.7zm33.723 8.773c0 1.073.185 2.099.554 3.078.368.978.875 1.82 1.517 2.525a7.436 7.436 0 0 0 2.323 1.705c.906.432 1.918.647 3.035.647 1.116 0 2.134-.215 3.05-.647.918-.432 1.697-1 2.34-1.705.643-.704 1.148-1.54 1.517-2.51a8.614 8.614 0 0 0 .554-3.093 8.273 8.273 0 0 0-.554-3.03 8.493 8.493 0 0 0-1.517-2.526 6.915 6.915 0 0 0-2.323-1.736c-.907-.42-1.93-.631-3.067-.631-1.117 0-2.129.21-3.035.63a6.92 6.92 0 0 0-2.323 1.737 8.5 8.5 0 0 0-1.517 2.526 8.272 8.272 0 0 0-.554 3.03m-9.915 0c0-2.336.376-4.504 1.129-6.503.752-2 1.8-3.72 3.147-5.161a15.203 15.203 0 0 1 4.72-3.425c1.802-.842 3.752-1.263 5.851-1.263 2.339 0 4.368.495 6.085 1.483 1.718.99 3.009 2.242 3.873 3.757v-4.451h9.915v31.158h-9.915V78.15c-.78 1.43-2.035 2.651-3.762 3.661-1.728 1.01-3.794 1.516-6.196 1.516-2.077 0-4.022-.421-5.834-1.263a15.017 15.017 0 0 1-4.737-3.441c-1.346-1.452-2.395-3.177-3.147-5.177-.753-1.999-1.129-4.167-1.129-6.503m36.704-8.773v-7.31h4.7v-9.195h9.554v9.195h6.837v7.312h-6.837v11.015c0 1.144.102 2.054.305 2.73.203.676.493 1.207.87 1.591a2.64 2.64 0 0 0 1.374.749 8.72 8.72 0 0 0 1.816.171c.406 0 .926-.015 1.557-.047.63-.03 1.18-.088 1.648-.172v7.62a36.13 36.13 0 0 1-2.671.349c-1.068.106-2.325.159-3.77.159a26.9 26.9 0 0 1-3.548-.25 8.077 8.077 0 0 1-3.46-1.32c-1.062-.71-1.94-1.804-2.633-3.278-.695-1.475-1.042-3.5-1.042-6.073V58.171h-4.7zm25.792 10.802v-18.38h9.723v15.872c0 2.217.392 4.02 1.177 5.411.784 1.39 2.188 2.086 4.212 2.086 2.024 0 3.517-.706 4.477-2.118.96-1.411 1.44-3.319 1.44-5.724V50.592h9.723v30.96h-9.723v-4.423c-.846 1.631-2.137 2.907-3.872 3.827-1.734.92-3.81 1.38-6.226 1.38-2.124 0-3.867-.335-5.231-1.004-1.364-.669-2.473-1.515-3.327-2.54-.978-1.193-1.618-2.573-1.92-4.14-.302-1.57-.453-3.462-.453-5.679m34.72 2.137h9.464c0 1.389.452 2.386 1.356 2.991.904.605 2.066.908 3.484.908 1.172 0 2.132-.198 2.882-.596.75-.398 1.126-1.05 1.126-1.958 0-.536-.16-.976-.478-1.318-.319-.342-.807-.648-1.465-.916a34.393 34.393 0 0 0-2.517-.888l-3.71-1.18a61.434 61.434 0 0 1-3.653-1.304c-1.14-.45-2.143-1.012-3.005-1.683a7.083 7.083 0 0 1-2.02-2.453c-.483-.964-.724-2.159-.724-3.584 0-1.636.318-3.05.955-4.246a8.143 8.143 0 0 1 2.683-2.956c1.15-.776 2.528-1.358 4.13-1.745 1.603-.388 3.37-.582 5.303-.582 2.363 0 4.398.272 6.104.817 1.705.546 3.103 1.29 4.193 2.233 1.088.944 1.895 2.055 2.42 3.334.523 1.279.786 2.652.786 4.12h-9.526c0-1.28-.324-2.275-.972-2.988-.647-.713-1.629-1.07-2.944-1.07-1.007 0-1.87.226-2.59.677-.719.45-1.078 1.127-1.078 2.028 0 .605.17 1.09.51 1.454.34.364.838.693 1.497.984.66.292 1.471.595 2.439.907l3.327 1.076a40.42 40.42 0 0 1 3.79 1.185c1.17.435 2.203.964 3.096 1.586a6.94 6.94 0 0 1 2.127 2.34c.524.938.786 2.129.786 3.57 0 1.903-.37 3.523-1.11 4.861a9.335 9.335 0 0 1-2.975 3.267c-1.243.84-2.687 1.443-4.331 1.808-1.645.365-3.37.547-5.179.547-4.5 0-7.99-.959-10.466-2.877-2.477-1.918-3.715-4.702-3.715-8.35" fill="#000" />
<path
d="M72.458 61.429c-7.431.427-12.088-1.299-19.52-.871a31.245 31.245 0 0 0-5.47.796C48.565 47.65 58.292 35.662 71.519 34.9c8.117-.467 16.23 4.53 16.67 12.642.433 7.973-5.664 13.307-15.73 13.886M52.503 89.46c-7.776.438-15.547-4.24-15.969-11.831-.415-7.462 5.427-12.454 15.07-12.996 7.118-.4 11.58 1.216 18.698.815a30.589 30.589 0 0 0 5.24-.745C74.493 77.528 65.175 88.748 52.503 89.46M62 .181C27.758.18 0 27.857 0 62s27.758 61.82 62 61.82c34.242 0 62-27.678 62-61.82C124 27.858 96.242.18 62 .18"
fill="#4360DF"
/>
<path
d="M152 71.11h9.464c0 1.389.452 2.386 1.356 2.991.905.605 2.066.908 3.484.908 1.172 0 2.132-.198 2.883-.596.75-.398 1.125-1.05 1.125-1.958 0-.536-.16-.976-.478-1.318-.319-.342-.807-.648-1.465-.916a34.317 34.317 0 0 0-2.517-.888l-3.71-1.18a61.345 61.345 0 0 1-3.653-1.304c-1.14-.45-2.142-1.012-3.005-1.683a7.086 7.086 0 0 1-2.02-2.453c-.483-.964-.724-2.159-.724-3.584 0-1.636.319-3.05.955-4.246a8.143 8.143 0 0 1 2.683-2.956c1.15-.776 2.528-1.358 4.13-1.745 1.604-.388 3.371-.582 5.303-.582 2.364 0 4.398.272 6.104.817 1.705.546 3.103 1.29 4.193 2.233 1.088.944 1.895 2.055 2.42 3.334.524 1.279.786 2.652.786 4.12h-9.526c0-1.28-.324-2.275-.972-2.988-.647-.713-1.628-1.07-2.943-1.07-1.007 0-1.87.226-2.59.677-.72.45-1.079 1.127-1.079 2.028 0 .605.17 1.09.51 1.454.34.364.839.693 1.497.984.66.292 1.472.595 2.44.907l3.326 1.076c1.355.356 2.619.75 3.79 1.185 1.17.435 2.203.964 3.096 1.586a6.936 6.936 0 0 1 2.127 2.34c.524.938.786 2.129.786 3.57 0 1.903-.37 3.523-1.11 4.861a9.342 9.342 0 0 1-2.975 3.267c-1.243.84-2.687 1.443-4.331 1.808-1.644.365-3.37.547-5.179.547-4.5 0-7.99-.959-10.466-2.877-2.477-1.918-3.715-4.702-3.715-8.35m28.768-12.938V50.86h4.7v-9.195h9.554v9.195h6.837v7.312h-6.837v11.015c0 1.144.102 2.054.305 2.73.203.676.493 1.207.87 1.591a2.64 2.64 0 0 0 1.374.749 8.744 8.744 0 0 0 1.816.171c.406 0 .926-.015 1.557-.047.63-.03 1.18-.088 1.648-.172v7.62a36.13 36.13 0 0 1-2.671.349c-1.068.106-2.325.159-3.77.159a26.9 26.9 0 0 1-3.548-.25 8.077 8.077 0 0 1-3.46-1.32c-1.062-.71-1.94-1.804-2.633-3.278-.695-1.475-1.042-3.5-1.042-6.073V58.171h-4.7zm33.723 8.773c0 1.073.185 2.099.554 3.078.368.978.875 1.82 1.517 2.525a7.436 7.436 0 0 0 2.323 1.705c.906.432 1.918.647 3.035.647 1.116 0 2.134-.215 3.05-.647.918-.432 1.697-1 2.34-1.705.643-.704 1.148-1.54 1.517-2.51a8.614 8.614 0 0 0 .554-3.093 8.273 8.273 0 0 0-.554-3.03 8.493 8.493 0 0 0-1.517-2.526 6.915 6.915 0 0 0-2.323-1.736c-.907-.42-1.93-.631-3.067-.631-1.117 0-2.129.21-3.035.63a6.92 6.92 0 0 0-2.323 1.737 8.5 8.5 0 0 0-1.517 2.526 8.272 8.272 0 0 0-.554 3.03m-9.915 0c0-2.336.376-4.504 1.129-6.503.752-2 1.8-3.72 3.147-5.161a15.203 15.203 0 0 1 4.72-3.425c1.802-.842 3.752-1.263 5.851-1.263 2.339 0 4.368.495 6.085 1.483 1.718.99 3.009 2.242 3.873 3.757v-4.451h9.915v31.158h-9.915V78.15c-.78 1.43-2.035 2.651-3.762 3.661-1.728 1.01-3.794 1.516-6.196 1.516-2.077 0-4.022-.421-5.834-1.263a15.017 15.017 0 0 1-4.737-3.441c-1.346-1.452-2.395-3.177-3.147-5.177-.753-1.999-1.129-4.167-1.129-6.503m36.704-8.773v-7.31h4.7v-9.195h9.554v9.195h6.837v7.312h-6.837v11.015c0 1.144.102 2.054.305 2.73.203.676.493 1.207.87 1.591a2.64 2.64 0 0 0 1.374.749 8.72 8.72 0 0 0 1.816.171c.406 0 .926-.015 1.557-.047.63-.03 1.18-.088 1.648-.172v7.62a36.13 36.13 0 0 1-2.671.349c-1.068.106-2.325.159-3.77.159a26.9 26.9 0 0 1-3.548-.25 8.077 8.077 0 0 1-3.46-1.32c-1.062-.71-1.94-1.804-2.633-3.278-.695-1.475-1.042-3.5-1.042-6.073V58.171h-4.7zm25.792 10.802v-18.38h9.723v15.872c0 2.217.392 4.02 1.177 5.411.784 1.39 2.188 2.086 4.212 2.086 2.024 0 3.517-.706 4.477-2.118.96-1.411 1.44-3.319 1.44-5.724V50.592h9.723v30.96h-9.723v-4.423c-.846 1.631-2.137 2.907-3.872 3.827-1.734.92-3.81 1.38-6.226 1.38-2.124 0-3.867-.335-5.231-1.004-1.364-.669-2.473-1.515-3.327-2.54-.978-1.193-1.618-2.573-1.92-4.14-.302-1.57-.453-3.462-.453-5.679m34.72 2.137h9.464c0 1.389.452 2.386 1.356 2.991.904.605 2.066.908 3.484.908 1.172 0 2.132-.198 2.882-.596.75-.398 1.126-1.05 1.126-1.958 0-.536-.16-.976-.478-1.318-.319-.342-.807-.648-1.465-.916a34.393 34.393 0 0 0-2.517-.888l-3.71-1.18a61.434 61.434 0 0 1-3.653-1.304c-1.14-.45-2.143-1.012-3.005-1.683a7.083 7.083 0 0 1-2.02-2.453c-.483-.964-.724-2.159-.724-3.584 0-1.636.318-3.05.955-4.246a8.143 8.143 0 0 1 2.683-2.956c1.15-.776 2.528-1.358 4.13-1.745 1.603-.388 3.37-.582 5.303-.582 2.363 0 4.398.272 6.104.817 1.705.546 3.103 1.29 4.193 2.233 1.088.944 1.895 2.055 2.42 3.334.523 1.279.786 2.652.786 4.12h-9.526c0-1.28-.324-2.275-.972-2.988-.647-.713-1.629-1.07-2.944-1.07-1.007 0-1.87.226-2.59.677-.719.45-1.078 1.127-1.078 2.028 0 .605.17 1.09.51 1.454.34.364.838.693 1.497.984.66.292 1.471.595 2.439.907l3.327 1.076a40.42 40.42 0 0 1 3.79 1.185c1.17.435 2.203.964 3.096 1.586a6.94 6.94 0 0 1 2.127 2.34c.524.938.786 2.129.786 3.57 0 1.903-.37 3.523-1.11 4.861a9.335 9.335 0 0 1-2.975 3.267c-1.243.84-2.687 1.443-4.331 1.808-1.645.365-3.37.547-5.179.547-4.5 0-7.99-.959-10.466-2.877-2.477-1.918-3.715-4.702-3.715-8.35"
fill="#000"
/>
</g>
</svg>
);

View File

@ -1,6 +1,6 @@
import axios from 'axios';
const repoFilter = (repo) => {
const repoFilter = repo => {
const { path } = repo;
if (path.includes('ideas')) {
const split = path.split('/');
@ -8,23 +8,22 @@ const repoFilter = (repo) => {
if (path.includes('README')) return true;
}
return false;
}
};
const convertToUrl = (repo) => {
const convertToUrl = repo => {
const { path } = repo;
const base = 'https://ideas.status.im/';
const suffix = path.split('.md')[0];
return `${base}${suffix}`;
}
};
const fetchContent = async () => {
const response = await axios.get('https://api.github.com/repos/status-im/ideas/git/trees/master?recursive=1');
return response['data']['tree'].filter(repoFilter).map(convertToUrl);
}
};
const pluckIdeas = async () => {
const data = await fetchContent();
return data;
}
};
export default pluckIdeas;

View File

@ -1,32 +1,32 @@
import web3 from "Embark/web3"
import web3 from 'Embark/web3';
import MiniMeTokenInterface from 'Embark/contracts/MiniMeTokenInterface';
import PollManager from 'Embark/contracts/PollManager';
import DappToken from 'Embark/contracts/DappToken';
const excluded = {
// PROPER_LIGHT_CLIENT_SUPPORT : 3,
// PROPER_LIGHT_CLIENT_SUPPORT : 3,
};
export const getBalance = async (startBlock) => {
export const getBalance = async startBlock => {
const { fromWei } = web3.utils;
const { balanceOfAt } = DappToken.methods;
const balance = await balanceOfAt(web3.eth.defaultAccount, startBlock - 1).call();
return fromWei(balance);
}
};
const fetchPollData = async (index, pollMethod) => {
const poll = await pollMethod(index).call({from: web3.eth.defaultAccount});
const poll = await pollMethod(index).call({ from: web3.eth.defaultAccount });
const blockInfo = await web3.eth.getBlock(poll._startBlock);
return { ...poll, idPoll: index, blockInfo };
}
};
export const getPolls = (number, pollMethod) => {
const polls = [];
for (let i = number-1; i >= 0; i--) {
const polls = [];
for (let i = number - 1; i >= 0; i--) {
polls.push(fetchPollData(i, pollMethod));
}
}
return Promise.all(polls.reverse());
}
};
const excludedPolls = new Set(Object.values(excluded));
const exclusionFilter = (poll, idx) => !excludedPolls.has(idx);

View File

@ -1,42 +1,40 @@
const BN = require('bn.js');
const numberToBN = require('number-to-bn');
const padLeft = (number, length) => {
var str = String(number);
while (str.length < length) {
str = '0' + str;
}
return str;
var str = String(number);
while (str.length < length) {
str = '0' + str;
}
return str;
};
const padRight = (number, length) => {
var str = String(number);
while (str.length < length) {
str += '0';
}
return str;
var str = String(number);
while (str.length < length) {
str += '0';
}
return str;
};
const fromTokenDecimals = (value, decimals) => {
value = numberToBN(value);
const pow = new BN(10, 10).pow(numberToBN(decimals));
const int = value.div(pow);
const dec = padLeft(value.mod(pow).toString(10), decimals).replace(/0+$/, '');
return int.toString(10) + (dec !== "" ? "." + dec : "");
value = numberToBN(value);
const pow = new BN(10, 10).pow(numberToBN(decimals));
const int = value.div(pow);
const dec = padLeft(value.mod(pow).toString(10), decimals).replace(/0+$/, '');
return int.toString(10) + (dec !== '' ? '.' + dec : '');
};
const toTokenDecimals = (value, decimals) => {
value = value.toString().split(".");
const pow = new BN(10, 10).pow(numberToBN(decimals));
const int = numberToBN(value[0]).mul(pow);
const dec = numberToBN(padRight(value.length > 1 ? value[1] : 0, decimals));
if(dec.toString(10).length > pow.toString(10).length) throw new Error("Too many decimal places");
return int.add(dec).toString(10);
value = value.toString().split('.');
const pow = new BN(10, 10).pow(numberToBN(decimals));
const int = numberToBN(value[0]).mul(pow);
const dec = numberToBN(padRight(value.length > 1 ? value[1] : 0, decimals));
if (dec.toString(10).length > pow.toString(10).length) throw new Error('Too many decimal places');
return int.add(dec).toString(10);
};
module.exports = {
fromTokenDecimals,
toTokenDecimals
fromTokenDecimals,
toTokenDecimals
};

3035
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,9 @@
"scripts": {
"solidity-coverage": "./node_modules/.bin/solidity-coverage",
"lint": "./node_modules/.bin/eslint app",
"test": "embark test"
"test": "embark test",
"precommit": "lint-staged",
"format": "prettier --write \"app/**/*.js\""
},
"repository": {
"type": "git",
@ -17,6 +19,12 @@
"url": "https://github.com/status-im/contracts/issues"
},
"homepage": "https://github.com/status-im/contracts#readme",
"lint-staged": {
"app/**/*.{js,json,css,md}": [
"prettier --write",
"git add"
]
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.0.0",
@ -30,8 +38,19 @@
"@babel/plugin-syntax-import-meta": "^7.0.0",
"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-react": "^7.11.1"
"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",
"husky": "^1.3.1",
"lint-staged": "^8.1.3",
"prettier": "^1.16.4"
},
"dependencies": {
"@date-io/date-fns": "0.0.2",