├── .babelrc
├── .eslintrc.js
├── .gitignore
├── README.md
├── client
├── actions
│ ├── judgeActions.js
│ ├── modalsActions.js
│ ├── sponsorActions.js
│ ├── teamActions.js
│ ├── types.js
│ └── userActions.js
├── components
│ ├── Admin
│ │ ├── AdminJudges
│ │ │ ├── AdminJudgesForm.jsx
│ │ │ ├── _index.scss
│ │ │ └── index.jsx
│ │ ├── AdminOptions
│ │ │ ├── AdminOptions.js
│ │ │ ├── Payout
│ │ │ │ ├── Payout.jsx
│ │ │ │ └── payout.scss
│ │ │ └── adminOptions.scss
│ │ ├── AdminSponsors
│ │ │ ├── AdminSponsorsForm.jsx
│ │ │ ├── _index.scss
│ │ │ └── index.jsx
│ │ ├── AdminTeams
│ │ │ ├── AdminTeamsForm.jsx
│ │ │ ├── _index.scss
│ │ │ └── index.jsx
│ │ ├── _index.scss
│ │ └── index.jsx
│ ├── App
│ │ ├── App.jsx
│ │ └── app.scss
│ ├── Decorative
│ │ ├── AddCircleIcon
│ │ │ └── AddCircleIcon.js
│ │ ├── CloseIcon
│ │ │ └── CloseIcon.jsx
│ │ ├── CubeLoader
│ │ │ ├── CubeLoader.jsx
│ │ │ └── cubeLoader.scss
│ │ ├── DollarIcon
│ │ │ └── index.jsx
│ │ ├── EthereumIcon
│ │ │ └── EthereumIcon.jsx
│ │ ├── Loader
│ │ │ ├── _index.scss
│ │ │ └── index.jsx
│ │ ├── MedalIcon
│ │ │ └── MedalIcon.jsx
│ │ └── StarIcon
│ │ │ └── index.jsx
│ ├── Footer
│ │ ├── Footer.js
│ │ └── footer.scss
│ ├── FormComonent
│ │ ├── FormComponent.jsx
│ │ └── forms.scss
│ ├── Header
│ │ ├── HeaderStatus
│ │ │ ├── _index.scss
│ │ │ └── index.jsx
│ │ ├── _index.scss
│ │ └── index.jsx
│ ├── Jury
│ │ ├── _index.scss
│ │ ├── assets
│ │ │ ├── 001-arrows.png
│ │ │ └── 002-arrows-1.png
│ │ └── index.jsx
│ ├── Landing
│ │ ├── _index.scss
│ │ └── index.jsx
│ ├── Modals
│ │ ├── JudgeModal.jsx
│ │ ├── ModalWrapper
│ │ │ ├── ModalWrapper.jsx
│ │ │ └── modalWrapper.scss
│ │ ├── SponsorModal.jsx
│ │ └── TeamModal.jsx
│ ├── NoMetamask
│ │ └── NoMetamask.jsx
│ ├── OpenModalButton
│ │ └── OpenModalButton.jsx
│ ├── Scoreboard
│ │ ├── Scoreboard.jsx
│ │ └── scoreboard.scss
│ └── Sponsor
│ │ ├── _index.scss
│ │ └── index.jsx
├── config
│ ├── Routes.jsx
│ ├── initWeb3.js
│ └── storeGenerator.js
├── external
│ └── particlesConfig.json
├── index.html
├── index.js
├── modules
│ ├── config.json
│ └── ethereumService.js
└── reducers
│ ├── index.js
│ ├── judgesReducer.js
│ ├── modalsReducer.js
│ ├── sponsorsReducer.js
│ ├── teamsReducer.js
│ └── userReducer.js
├── dist
├── 521fb616232adb3eb64828d5a3d820a1.png
├── eecc5a0815a9233ebd31eb7d77eda4b1.png
├── index.html
├── main.css
└── main.js
├── package.json
├── solidity
└── DecenterHackathon.sol
└── webpack
├── webpack-dev.config.js
└── webpack-prod.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"],
3 | "plugins": ["transform-object-rest-spread", "transform-exponentiation-operator"]
4 | }
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'extends': 'eslint-config-airbnb',
3 | 'env': {
4 | 'browser': true
5 | },
6 | 'rules': {
7 | 'jsx-a11y/href-no-hash': 'off',
8 | 'jsx-a11y/anchor-is-valid': ['warn', { 'aspects': ['invalidHref'] }],
9 | 'jsx-a11y/label-has-for': 0,
10 | 'jsx-a11y/no-autofocus': 0,
11 | 'comma-dangle': 0,
12 | 'react/forbid-prop-types': 0,
13 | 'react/jsx-filename-extension': 0,
14 | 'arrow-parens': 0,
15 | 'import/extensions': 0,
16 | 'no-unused-vars': 1,
17 | 'no-new': 0,
18 | 'no-underscore-dangle': 0,
19 | 'prefer-template': 0,
20 | 'keyword-spacing': 0,
21 | 'no-plusplus': 0,
22 | 'prefer-const': 0
23 | },
24 | 'plugins': [
25 | 'react', 'import'
26 | ],
27 | 'globals': {
28 | 'web3': true
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | yarn.lock
2 | .DS_Store
3 | .idea/
4 | node_modules/
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Smart Hackathon DApp
2 |
3 | This is a fully functional DApp that can be used to automate every aspect of a typical hackathon.
4 |
5 | Administrator (contract owner) can add jury members, jury members can vote, sponsors can donate ETH to the prize pool and contestents can register teams and receive prizes from the pool.
6 |
7 | The applications consistes of React-based front-end that interacts with Ethereum smart contracts (written in Solidity) via web3.
8 |
9 | ## Development
10 | to install all modules run ```yarn```
11 |
12 | to start dev server on localhost:8080 run ```yarn dev```
13 |
14 | to build production run ```yarn production```. Production code will be generated in the dist directory
15 |
--------------------------------------------------------------------------------
/client/actions/judgeActions.js:
--------------------------------------------------------------------------------
1 | import {
2 | NEW_JUDGE,
3 | JUDGES_FETCH,
4 | JUDGES_SUCCESS,
5 | JUDGES_ERROR,
6 | ADD_JUDGE,
7 | ADD_JUDGE_SUCCESS,
8 | ADD_JUDGE_ERROR
9 | } from './types';
10 | import toggleModal from './modalsActions';
11 |
12 | import * as eth from '../modules/ethereumService';
13 |
14 | const judgesFormValidator = (values) => {
15 | const errors = {};
16 |
17 | if (!values.name) errors.name = 'Required';
18 | if (!values.address) errors.address = 'Required';
19 |
20 | if (values.address && !web3.isAddress(values.address)) errors.address = 'Ethereum address is not valid';
21 |
22 | return errors;
23 | };
24 |
25 | const submitAddJudgesForm = (judge) => (dispatch) => {
26 | dispatch({ type: ADD_JUDGE });
27 |
28 | eth._registerJuryMember(judge.name, judge.address)
29 | .then((res) => {
30 | dispatch({ type: ADD_JUDGE_SUCCESS, payload: { judge: res } });
31 | dispatch(toggleModal(location.hash, false));
32 | })
33 | .catch((error) => {
34 | dispatch({ type: ADD_JUDGE_ERROR, payload: { addJudgeError: error.message.toString() } });
35 | });
36 | };
37 |
38 | const fetchJudges = () => (dispatch) => {
39 | dispatch({ type: JUDGES_FETCH });
40 | eth.getJuries()
41 | .then((res) => {
42 | dispatch({
43 | type: JUDGES_SUCCESS,
44 | judges: res
45 | });
46 | })
47 | .catch((error) => {
48 | console.log(error);
49 | const errorMessage = error.message ? error.message.toString() : error;
50 | dispatch({
51 | type: JUDGES_ERROR,
52 | error: errorMessage
53 | });
54 | });
55 | };
56 |
57 | const judgeEventListener = () => (dispatch) => {
58 | eth.JuryMemberAddedEvent((error, data) => {
59 | if (!error) {
60 | console.log(data);
61 | dispatch({
62 | type: NEW_JUDGE,
63 | event: data,
64 | });
65 | }
66 | });
67 | };
68 |
69 | module.exports = {
70 | fetchJudges,
71 | judgesFormValidator,
72 | submitAddJudgesForm,
73 | judgeEventListener,
74 | };
75 |
--------------------------------------------------------------------------------
/client/actions/modalsActions.js:
--------------------------------------------------------------------------------
1 | import { reset } from 'redux-form';
2 | import { TOGGLE_MODAL } from './types';
3 | import JudgeModal from '../components/Modals/JudgeModal';
4 | import teamModal from '../components/Modals/TeamModal';
5 | import sponsorModal from '../components/Modals/SponsorModal';
6 |
7 | export default (routePath, state) => (
8 | (dispatch) => {
9 | switch (routePath) {
10 | case '#/admin/teams':
11 | if (state === false) dispatch(reset('teamsForm'));
12 | dispatch({ type: TOGGLE_MODAL, payload: { state, modalComponent: teamModal } });
13 | return true;
14 | case '#/admin/sponsors':
15 | if (state === false) dispatch(reset('sponsorsForm'));
16 | dispatch({ type: TOGGLE_MODAL, payload: { state, modalComponent: sponsorModal } });
17 | return true;
18 | case '#/admin/judges':
19 | if (state === false) dispatch(reset('judgesForm'));
20 | dispatch({ type: TOGGLE_MODAL, payload: { state, modalComponent: JudgeModal } });
21 | return true;
22 | default:
23 | return false;
24 | }
25 | }
26 | );
27 |
--------------------------------------------------------------------------------
/client/actions/sponsorActions.js:
--------------------------------------------------------------------------------
1 | import {
2 | NEW_SPONSOR, SPONSORS_FETCH, SPONSORS_SUCCESS, SPONSORS_ERROR, ADD_SPONSOR, ADD_SPONSOR_SUCCESS,
3 | ADD_SPONSOR_ERROR, SPONSORS_PRIZE_ETH, SPONSORS_PRIZE_EUR
4 | } from './types';
5 | import toggleModal from './modalsActions';
6 |
7 | import * as eth from '../modules/ethereumService';
8 |
9 | const isUriImage = (uri) => {
10 | if (!uri) return false;
11 | const uriNoParam = uri.split('?')[0];
12 | const parts = uriNoParam.split('.');
13 | const extension = parts[parts.length - 1];
14 | const imageTypes = ['jpg', 'jpeg', 'tiff', 'png', 'gif', 'bmp'];
15 | return imageTypes.indexOf(extension) !== -1;
16 | };
17 |
18 | const isURL = (str) => {
19 | const regexp = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/gm; // eslint-disable-line
20 | return regexp.test(str);
21 | };
22 |
23 | const sponsorsFormValidator = (values) => {
24 | const errors = {};
25 |
26 | if (!values.name) errors.name = 'Required';
27 | if (!values.amount) errors.amount = 'Required';
28 | if (!values.logoUrl) errors.logoUrl = 'Required';
29 | if (!values.websiteUrl) errors.websiteUrl = 'Required';
30 |
31 | if (values.logoUrl && !isUriImage(values.logoUrl)) errors.logoUrl = 'Url does not contain valid image extension';
32 | if (values.websiteUrl && !isURL(values.websiteUrl)) errors.websiteUrl = 'Url is not valid';
33 |
34 | if (values.amount) {
35 | const commaError = values.amount && values.amount.indexOf(',') > 0;
36 | const nanError = isNaN(parseFloat(values.amount));
37 | const amountError = parseFloat(values.amount) < 0.1;
38 |
39 | if (commaError) errors.amount = 'Use a full stop as a delimiter instead of a comma';
40 | if (nanError) errors.amount = 'The provided input is not a number';
41 | if (amountError) errors.amount = 'The donation can\'t be lower than 0.1 ETH';
42 | }
43 |
44 | return errors;
45 | };
46 |
47 | const submitAddSponsorsForm = (sponsor) => (dispatch) => {
48 | dispatch({ type: ADD_SPONSOR });
49 |
50 | eth._contributeToPrizePool(
51 | sponsor.name,
52 | parseFloat(sponsor.amount),
53 | sponsor.websiteUrl,
54 | sponsor.logoUrl
55 | )
56 | .then((res) => {
57 | dispatch({ type: ADD_SPONSOR_SUCCESS, payload: { sponsor: res } });
58 | dispatch(toggleModal(location.hash, false));
59 | })
60 | .catch((error) => {
61 | dispatch({ type: ADD_SPONSOR_ERROR, payload: { addSponsorError: error.message.toString() } });
62 | });
63 | };
64 |
65 | const fetchSponsors = () => (dispatch) => {
66 | dispatch({ type: SPONSORS_FETCH });
67 | eth.getSponsors()
68 | .then((res) => {
69 | dispatch({
70 | type: SPONSORS_SUCCESS,
71 | sponsors: res
72 | });
73 | })
74 | .catch((error) => {
75 | console.log(error);
76 | const errorMessage = error.message ? error.message.toString() : error;
77 | dispatch({
78 | type: SPONSORS_ERROR,
79 | error: errorMessage
80 | });
81 | });
82 | };
83 |
84 | const fetchPrizePoolSize = () => (dispatch) => {
85 | eth.getPrizePoolSize()
86 | .then((res) => {
87 | const prize = web3.fromWei(res).toString();
88 | console.log(prize);
89 | dispatch({
90 | type: SPONSORS_PRIZE_ETH,
91 | prize
92 | });
93 | fetch('https://api.kraken.com/0/public/Ticker?pair=xethzeur', {
94 | method: 'get'
95 | })
96 | .then(response => response.json())
97 | .then((data) => {
98 | let eurPrize = data.result.XETHZEUR.c[0] * prize;
99 | eurPrize = eurPrize.toFixed(2);
100 | dispatch({
101 | type: SPONSORS_PRIZE_EUR,
102 | prize: eurPrize.toString(),
103 | });
104 | })
105 | .catch((error) => {
106 | console.log(error);
107 | });
108 | })
109 | .catch((error) => {
110 | console.log(error);
111 | });
112 | };
113 |
114 | const sponsorEventListener = () => (dispatch) => {
115 | eth.SponsorshipReceivedEvent((error, data) => {
116 | if (!error) {
117 | dispatch({
118 | type: NEW_SPONSOR,
119 | event: data,
120 | });
121 | dispatch(fetchPrizePoolSize());
122 | }
123 | });
124 | };
125 |
126 | module.exports = {
127 | fetchSponsors,
128 | sponsorsFormValidator,
129 | submitAddSponsorsForm,
130 | sponsorEventListener,
131 | fetchPrizePoolSize,
132 | };
133 |
--------------------------------------------------------------------------------
/client/actions/teamActions.js:
--------------------------------------------------------------------------------
1 | import {
2 | NEW_TEAM,
3 | TEAM_UP,
4 | TEAM_DOWN,
5 | TEAMS_FETCH,
6 | TEAMS_SUCCESS,
7 | TEAMS_ERROR,
8 | ADD_TEAM,
9 | ADD_TEAM_ERROR,
10 | ADD_TEAM_SUCCESS,
11 | VOTE,
12 | VOTE_SUCCESS,
13 | VOTE_ERROR
14 | } from './types';
15 | import toggleModal from './modalsActions';
16 |
17 | import * as eth from '../modules/ethereumService';
18 |
19 | const teamsFormValidator = (values) => {
20 | const errors = {};
21 |
22 | if (!values.name) errors.name = 'Required';
23 | if (!values.address) errors.address = 'Required';
24 |
25 | if (values.address && !web3.isAddress(values.address)) errors.address = 'Ethereum address is not valid';
26 |
27 | if (!values.teamMembers) errors.teamMembers = 'Required';
28 |
29 | if (values.teamMembers) {
30 | const teamMembers = values.teamMembers.split(', ');
31 | teamMembers.reduce((sum, val) => {
32 | if (val.indexOf(',') > 0) errors.teamMembers = 'Team members are not correctly separated';
33 | return sum.concat(`, ${val}`);
34 | }, '');
35 | }
36 |
37 | return errors;
38 | };
39 |
40 | const submitAddTeamsForm = (team) => (dispatch) => {
41 | dispatch({ type: ADD_TEAM });
42 |
43 | eth._registerTeam(team.name, team.address, team.teamMembers, !team.excludeFromPrize)
44 | .then((res) => {
45 | dispatch({ type: ADD_TEAM_SUCCESS, payload: { team: res } });
46 | dispatch(toggleModal(location.hash, false));
47 | })
48 | .catch((error) => {
49 | dispatch({ type: ADD_TEAM_ERROR, payload: { addTeamError: error.message.toString() } });
50 | });
51 | };
52 |
53 | const fetchTeams = () => (dispatch) => {
54 | dispatch({ type: TEAMS_FETCH });
55 |
56 | eth.getTeams()
57 | .then((res) => {
58 | dispatch({ type: TEAMS_SUCCESS, teams: res });
59 | })
60 | .catch((error) => {
61 | console.log(error);
62 | const errorMessage = error.message ? error.message.toString() : error;
63 | dispatch({
64 | type: TEAMS_ERROR,
65 | error: errorMessage
66 | });
67 | });
68 | };
69 |
70 | const fetchTeamScores = () => (dispatch) => {
71 | dispatch({ type: TEAMS_FETCH });
72 | eth.getTeams()
73 | .then((teams) => {
74 | eth.getTeamScores()
75 | .then((events) => {
76 | let result = {};
77 | for (let i = 0; i < events.length; i++) {
78 | let teamAddress = events[i].args.teamAddress;
79 | let juryMemberName = events[i].args.juryMemberName;
80 | let points = parseInt(events[i].args.points.toString(10), 10);
81 |
82 | if (result[teamAddress] !== undefined) {
83 | result[teamAddress].totalScore += points;
84 | result[teamAddress].scoreBreakdown.push({ juryMemberName, points });
85 | } else {
86 | result[teamAddress] = {
87 | totalScore: points,
88 | scoreBreakdown: [{
89 | juryMemberName,
90 | points
91 | }]
92 | };
93 | }
94 | }
95 |
96 | let teamsWithPoints = teams.map((item) => {
97 | let score = result[item.args.teamAddress];
98 | let scoredItem = item;
99 | scoredItem.args.totalScore = score.totalScore;
100 | scoredItem.args.scoreBreakdown = score.scoreBreakdown;
101 |
102 | return scoredItem;
103 | }).sort((a, b) => b.args.totalScore - a.args.totalScore);
104 |
105 | dispatch({ type: TEAMS_SUCCESS, teams: teamsWithPoints });
106 | })
107 | .catch((error) => {
108 | console.log(error);
109 | const errorMessage = error.message ? error.message.toString() : error;
110 | console.log(errorMessage);
111 | });
112 | })
113 | .catch(error => console.log(error));
114 | };
115 |
116 | const teamsEventListener = () => (dispatch) => {
117 | eth.TeamRegisteredEvent((error, data) => {
118 | if (!error) {
119 | console.log(data);
120 | dispatch({
121 | type: NEW_TEAM,
122 | event: data,
123 | });
124 | }
125 | });
126 | };
127 |
128 | const moveTeamUp = (index) => {
129 | if (index === 0) {
130 | return {
131 | type: '',
132 | };
133 | }
134 |
135 | return {
136 | type: TEAM_UP,
137 | index
138 | };
139 | };
140 |
141 | const moveTeamDown = (index) => (dispatch, getState) => {
142 | if (index === getState().teams.teams.length - 1) {
143 | return dispatch({
144 | type: ''
145 | });
146 | }
147 |
148 | return dispatch({
149 | type: TEAM_DOWN,
150 | index
151 | });
152 | };
153 |
154 | const vote = (votes) => dispatch => {
155 | dispatch({ type: VOTE });
156 |
157 | eth._vote(votes)
158 | .then(data => {
159 | dispatch({ type: VOTE_SUCCESS });
160 | })
161 | .catch(error => {
162 | dispatch({ type: VOTE_ERROR });
163 | });
164 | };
165 |
166 | module.exports = {
167 | fetchTeamScores,
168 | teamsEventListener,
169 | fetchTeams,
170 | teamsFormValidator,
171 | submitAddTeamsForm,
172 | moveTeamUp,
173 | moveTeamDown,
174 | vote,
175 | };
176 |
--------------------------------------------------------------------------------
/client/actions/types.js:
--------------------------------------------------------------------------------
1 | export const TOGGLE_MODAL = 'close_modal';
2 |
3 | export const ADD_TEAM = 'add_team';
4 | export const ADD_TEAM_SUCCESS = 'add_team_success';
5 | export const ADD_TEAM_ERROR = 'add_team_error';
6 |
7 | export const USER_CHECKING = 'user_checking';
8 | export const USER_FOUND = 'user_found';
9 |
10 | export const TEAMS_FETCH = 'teams_fetch';
11 | export const TEAMS_SUCCESS = 'teams_success';
12 | export const TEAMS_ERROR = 'teams_error';
13 | export const TEAM_UP = 'team_up';
14 | export const TEAM_DOWN = 'team_down';
15 | export const NEW_TEAM = 'new_team';
16 |
17 | export const ADD_SPONSOR = 'add_sponsor';
18 | export const ADD_SPONSOR_SUCCESS = 'add_sponsor_success';
19 | export const ADD_SPONSOR_ERROR = 'add_sponsor_error';
20 |
21 | export const SPONSORS_FETCH = 'sponsors_fetch';
22 | export const SPONSORS_SUCCESS = 'sponsors_success';
23 | export const SPONSORS_ERROR = 'sponsors_error';
24 | export const NEW_SPONSOR = 'new_sponsors';
25 | export const SPONSORS_PRIZE_ETH = 'sponsors_prize_eth';
26 | export const SPONSORS_PRIZE_EUR = 'sponsors_prize_eur';
27 |
28 | export const ADD_JUDGE = 'add_judge';
29 | export const ADD_JUDGE_SUCCESS = 'add_judge_success';
30 | export const ADD_JUDGE_ERROR = 'add_judge_error';
31 |
32 | export const JUDGES_FETCH = 'judges_fetch';
33 | export const JUDGES_SUCCESS = 'judges_success';
34 | export const JUDGES_ERROR = 'judges_error';
35 | export const NEW_JUDGE = 'new_judge';
36 |
37 | export const PHASE_FETCH = 'phase_fetch';
38 | export const PHASE_FETCH_SUCCESS = 'phase_fetch_success';
39 | export const PHASE_FETCH_ERROR = 'phase_fetch_error';
40 | export const PHASE_CHANGE = 'phase_change';
41 | export const UPDATE_PHASE = 'update_phase';
42 |
43 | export const CHANGE_PHASE = 'change_phase';
44 | export const CHANGE_PHASE_SUCCESS = 'change_phase_success';
45 | export const CHANGE_PHASE_ERROR = 'change_phase_error';
46 |
47 | export const SUBMIT_PAYOUT = 'submit_payment';
48 | export const SUBMIT_PAYOUT_SUCCESS = 'submit_payment_success';
49 | export const SUBMIT_PAYOUT_ERROR = 'submit_payment_error';
50 |
51 | export const VOTE = 'jury_vote';
52 | export const VOTE_SUCCESS = 'jury_vote_success';
53 | export const VOTE_ERROR = 'jury_vote_error';
54 |
55 | export const ALREADY_VOTED = 'already_voted';
56 |
--------------------------------------------------------------------------------
/client/actions/userActions.js:
--------------------------------------------------------------------------------
1 | import {
2 | PHASE_FETCH, PHASE_FETCH_SUCCESS, PHASE_FETCH_ERROR, CHANGE_PHASE,
3 | CHANGE_PHASE_SUCCESS, CHANGE_PHASE_ERROR, USER_CHECKING, USER_FOUND,
4 | SUBMIT_PAYOUT, SUBMIT_PAYOUT_ERROR, SUBMIT_PAYOUT_SUCCESS, UPDATE_PHASE,
5 | ALREADY_VOTED
6 | } from './types';
7 |
8 | import * as eth from '../modules/ethereumService';
9 |
10 | const payoutTeams = (sortedTeams) => (dispatch) => {
11 | dispatch({ type: SUBMIT_PAYOUT });
12 |
13 | for (let i = 0; i < sortedTeams.length - 1; i += 1) {
14 | if (sortedTeams[i].points < sortedTeams[i + 1].points) {
15 | return dispatch({
16 | type: SUBMIT_PAYOUT_ERROR,
17 | message: 'Teams are not sorted'
18 | });
19 | }
20 | }
21 |
22 | const teamAddresses = sortedTeams.map((elem) => (elem.address));
23 |
24 | return eth._payoutPrizes(teamAddresses)
25 | .then(() => {
26 | dispatch({ type: SUBMIT_PAYOUT_SUCCESS });
27 | })
28 | .catch((error) => {
29 | console.log(error);
30 | dispatch({
31 | type: SUBMIT_PAYOUT_ERROR,
32 | message: 'Payout prizes contract error.'
33 | });
34 | });
35 | };
36 |
37 | const fetchPhase = () => (dispatch) => {
38 | dispatch({ type: PHASE_FETCH });
39 |
40 | eth.getPhase()
41 | .then((res) => {
42 | const phase = parseFloat(res);
43 | dispatch({ type: PHASE_FETCH_SUCCESS, payload: { phase } });
44 | })
45 | .catch((error) => {
46 | dispatch({ type: PHASE_FETCH_ERROR, payload: { error } });
47 | });
48 | };
49 |
50 | const changePhase = () => (dispatch) => {
51 | dispatch({ type: CHANGE_PHASE });
52 |
53 | eth._switchToNextPeriod()
54 | .then((res) => {
55 | dispatch({ type: CHANGE_PHASE_SUCCESS, });
56 | })
57 | .catch((error) => {
58 | console.error(error);
59 | dispatch({ type: CHANGE_PHASE_ERROR, payload: { error: error.message.toString() } });
60 | });
61 | };
62 |
63 | const checkUser = () => (dispatch) => {
64 | dispatch({ type: USER_CHECKING });
65 | eth.getUserTypeWithTimeout()
66 | .then((res) => {
67 | console.log(res);
68 | dispatch({
69 | type: USER_FOUND,
70 | userType: res
71 | });
72 | })
73 | .catch((error) => {
74 | console.log(error);
75 | });
76 | };
77 |
78 | const periodChangedListener = () => (dispatch) => {
79 | eth.PeriodChangedEvent((error, event) => {
80 | if (!error) {
81 | console.log(event);
82 | const phase = parseFloat(event.args.newPeriod.toString());
83 | console.log(phase);
84 | dispatch({
85 | type: UPDATE_PHASE,
86 | payload: {
87 | phase,
88 | }
89 | });
90 | }
91 | });
92 | };
93 |
94 | const checkIfJuryVoted = () => (dispatch) => {
95 | eth.checkJuryVoted((error) => {
96 | if (!error) {
97 | dispatch({ type: ALREADY_VOTED });
98 | }
99 | });
100 | };
101 |
102 | module.exports = {
103 | periodChangedListener,
104 | checkUser,
105 | fetchPhase,
106 | changePhase,
107 | payoutTeams,
108 | checkIfJuryVoted
109 | };
110 |
--------------------------------------------------------------------------------
/client/components/Admin/AdminJudges/AdminJudgesForm.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Field, reduxForm } from 'redux-form';
3 | import { connect } from 'react-redux';
4 | import formGroup from '../../FormComonent/FormComponent';
5 | import { judgesFormValidator, submitAddJudgesForm } from '../../../actions/judgeActions';
6 | import CubeLoader from '../../Decorative/CubeLoader/CubeLoader';
7 |
8 | require('../../FormComonent/forms.scss');
9 |
10 | let JudgesTeamsForm = ({ handleSubmit, pristine, submittingForm, invalid, addJudgeError }) => ( // eslint-disable-line
11 |
45 | );
46 |
47 | const mapStateToProps = (state) => ({
48 | submittingForm: state.judges.submitting,
49 | addJudgeError: state.judges.addJudgeError
50 | });
51 |
52 | const validate = judgesFormValidator;
53 |
54 | JudgesTeamsForm = reduxForm({ form: 'judgesForm', validate })(JudgesTeamsForm);
55 |
56 | export default connect(mapStateToProps, { onSubmit: submitAddJudgesForm })(JudgesTeamsForm);
57 |
--------------------------------------------------------------------------------
/client/components/Admin/AdminJudges/_index.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DecenterApps/Smart-Hackathon-DApp/31e6419a47c1a6c9885e35b359466c7fa26eb032/client/components/Admin/AdminJudges/_index.scss
--------------------------------------------------------------------------------
/client/components/Admin/AdminJudges/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import PropTypes from 'prop-types';
5 | import Loader from '../../Decorative/Loader/index.jsx';
6 | import OpenModalButton from '../../OpenModalButton/OpenModalButton';
7 | import judgeActions from '../../../actions/judgeActions';
8 |
9 | class AdminJudges extends Component {
10 | constructor(props) {
11 | super(props);
12 |
13 | this.state = {};
14 | }
15 | componentWillMount() {
16 | this.props.fetchJudges();
17 | }
18 | render() {
19 | return (
20 |
21 | {
22 | this.props.judges.isFetching &&
23 |
24 |
Loading
25 |
26 | }
27 | {
28 | !this.props.judges.isFetching &&
29 | this.props.judges.error &&
30 |
31 |
{this.props.judges.error.toString()}
32 |
33 | }
34 | {
35 | !this.props.judges.isFetching &&
36 | !this.props.judges.error &&
37 | !this.props.judges.judges.length > 0 &&
38 |
39 |
40 |
No judges
41 |
42 |
43 |
44 | }
45 | {
46 | !this.props.judges.isFetching &&
47 | !this.props.judges.error &&
48 | this.props.judges.judges.length > 0 &&
49 |
65 | }
66 |
67 | );
68 | }
69 | }
70 |
71 | AdminJudges.propTypes = {
72 | judges: PropTypes.shape({
73 | judges: PropTypes.array,
74 | isFetching: PropTypes.bool,
75 | error: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
76 | }),
77 | fetchJudges: PropTypes.func.isRequired,
78 | };
79 | AdminJudges.defaultProps = {
80 | judges: {
81 | judges: []
82 | },
83 | isFetching: true,
84 | };
85 |
86 | const mapStateToProps = state => state;
87 | const mapDispatchToProps = dispatch => bindActionCreators({
88 | ...judgeActions
89 | }, dispatch);
90 |
91 | export default connect(
92 | mapStateToProps,
93 | mapDispatchToProps,
94 | )(AdminJudges);
95 |
96 |
--------------------------------------------------------------------------------
/client/components/Admin/AdminOptions/AdminOptions.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 | import { changePhase } from '../../../actions/userActions';
5 | import CubeLoader from '../../Decorative/CubeLoader/CubeLoader';
6 | import Payout from './Payout/Payout';
7 |
8 | require('./adminOptions.scss');
9 |
10 | const AdminOptions = ({
11 | $changePhase, changingPhase, changingError, phase, lastPhaseIndex
12 | }) => (
13 |
14 | {
15 | phase < lastPhaseIndex - 1 &&
16 |
17 |
Switch to the next period:
18 |
19 | {changingPhase && }
20 | {changingPhase ? 'Changing' : 'Go'}
21 |
22 |
23 | {
24 | changingError &&
{changingError}
25 | }
26 |
27 |
28 | }
29 | {
30 | phase === lastPhaseIndex &&
31 |
32 | The last period is active. It can not be changed
33 |
34 | }
35 | {
36 | phase === 3 &&
37 | }
38 |
39 | );
40 |
41 | AdminOptions.propTypes = {
42 | $changePhase: PropTypes.func.isRequired,
43 | changingPhase: PropTypes.bool.isRequired,
44 | changingError: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired,
45 | phase: PropTypes.number.isRequired,
46 | lastPhaseIndex: PropTypes.number.isRequired
47 | };
48 |
49 | const mapStateToProps = (state) => ({
50 | changingPhase: state.user.changingPhase,
51 | changingError: state.user.changingError,
52 | phase: state.user.phase,
53 | lastPhaseIndex: state.user.phases.length - 1
54 | });
55 |
56 | export default connect(mapStateToProps, { $changePhase: changePhase })(AdminOptions);
57 |
--------------------------------------------------------------------------------
/client/components/Admin/AdminOptions/Payout/Payout.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 | import { bindActionCreators } from 'redux';
5 |
6 | import * as teamActions from '../../../../actions/teamActions';
7 | import userActions from '../../../../actions/userActions';
8 |
9 | import CubeLoader from '../../../Decorative/CubeLoader/CubeLoader';
10 | import Loader from '../../../Decorative/Loader/index';
11 | import arrowup from '../../../Jury/assets/001-arrows.png';
12 | import arrowdown from '../../../Jury/assets/002-arrows-1.png';
13 |
14 | require('./payout.scss');
15 |
16 | class Payout extends Component {
17 | constructor(props) {
18 | super(props);
19 |
20 | this.state = {};
21 |
22 | this.payoutTeams = this.payoutTeams.bind(this);
23 | }
24 |
25 | componentDidMount() {
26 | this.props.fetchTeamScores();
27 | }
28 |
29 | payoutTeams() {
30 | // fix after points implementation
31 | const votes = this.props.teams.teams.map((elem, i) => ({
32 | address: elem.args.teamAddress,
33 | points: elem.args.totalScore,
34 | }));
35 |
36 | this.props.payoutTeams(votes);
37 | }
38 |
39 | render() {
40 | return (
41 |
42 | {
43 | this.props.teams.isFetching &&
44 |
45 |
Loading
46 |
47 | }
48 | {
49 | !this.props.teams.isFetching &&
50 | this.props.teams.error &&
51 |
52 |
{this.props.teams.error.toString()}
53 |
54 | }
55 | {
56 | !this.props.teams.isFetching &&
57 | !this.props.teams.error &&
58 | !this.props.teams.teams.length > 0 &&
59 |
60 |
61 |
No teams
62 |
63 |
64 | }
65 |
66 | {
67 | !this.props.teams.isFetching &&
68 | !this.props.teams.error &&
69 | this.props.teams.teams.length > 0 &&
70 |
71 | Payout reward to teams:
72 |
73 | {this.props.user.submittingPayout && }
74 | {this.props.user.submittingPayout ? 'Paying Out' : 'Payout'}
75 |
76 |
77 | }
78 |
79 | {
80 | this.props.teams &&
81 | this.props.user.submittingPayoutError &&
82 | !this.props.user.submittingPayout &&
83 |
{this.props.user.submittingPayoutError}
84 | }
85 |
86 | {
87 | !this.props.teams.isFetching &&
88 | !this.props.teams.error &&
89 | this.props.teams.teams.length > 0 &&
90 |
91 |
92 | Scoreboard
93 |
94 | * You can switch the teams with the same number of points
95 |
96 |
97 |
98 | {
99 | this.props.teams.teams.map((team, i) => (
100 |
101 |
{i + 1}.
102 |
{team.args.teamName}
103 |
{team.args.teamAddress}
104 |
{team.args.totalScore} points
105 |
this.props.moveTeamDown(i)}
108 | role="button"
109 | tabIndex="-1"
110 | >
111 |
112 |
113 |
this.props.moveTeamUp(i)}
116 | role="button"
117 | tabIndex="-1"
118 | >
119 |
120 |
121 |
122 | ))
123 | }
124 |
125 |
126 | }
127 |
128 | );
129 | }
130 | }
131 |
132 | Payout.propTypes = {
133 | teams: PropTypes.shape({
134 | isFetching: PropTypes.bool.isRequired,
135 | teams: PropTypes.array,
136 | error: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
137 | }),
138 | user: PropTypes.shape({
139 | submittingPayout: PropTypes.bool.isRequired,
140 | submittingPayoutError: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired
141 | }).isRequired,
142 | moveTeamUp: PropTypes.func.isRequired,
143 | moveTeamDown: PropTypes.func.isRequired,
144 | payoutTeams: PropTypes.func.isRequired,
145 | fetchTeamScores: PropTypes.func.isRequired,
146 | };
147 |
148 | Payout.defaultProps = {
149 | teams: {
150 | isFetching: true,
151 | teams: [1]
152 | }
153 | };
154 |
155 | const mapStateToProps = state => state;
156 | const mapDispatchToProps = dispatch => bindActionCreators({
157 | ...teamActions,
158 | ...userActions,
159 | }, dispatch);
160 |
161 | export default connect(
162 | mapStateToProps,
163 | mapDispatchToProps,
164 | )(Payout);
165 |
--------------------------------------------------------------------------------
/client/components/Admin/AdminOptions/Payout/payout.scss:
--------------------------------------------------------------------------------
1 | .payout-wrapper {
2 | border-top: 1px solid #d0d0d0;
3 |
4 | .payout-error {
5 | color: #F57170;
6 | font-size: 16px;
7 | margin-left: 20px;
8 | }
9 |
10 | button {
11 | display: inline-flex;
12 | align-items: center;
13 | justify-content: center;
14 | color: white;
15 | background: #673AB7;
16 | padding: 10px 20px;
17 | font-size: 16px;
18 | border-radius: 3px;
19 | margin-left: 10px;
20 |
21 | &:hover {
22 | background: lighten(#673AB7, 15);
23 | }
24 |
25 | &:disabled {
26 | user-select: none;
27 | cursor: not-allowed;
28 | background: lighten(#673AB7, 30);
29 | }
30 | }
31 |
32 | .pay-button-wrapper {
33 | font-size: 18px;
34 | font-weight: 700;
35 | margin: 20px;
36 | }
37 |
38 | .table-header {
39 | display: flex;
40 | justify-content: space-between;
41 | align-items: center;
42 | border-top: 1px solid #d0d0d0;
43 | font-size: 20px;
44 | padding: 30px;
45 |
46 | .tip {
47 | font-size: 14px;
48 | }
49 | }
50 |
51 | .table {
52 | border-bottom: 0;
53 | margin-bottom: 0;
54 |
55 | .team-name {
56 | flex-grow: 0.5;
57 | }
58 |
59 | .arrows, .team-rank, .team-points {
60 | flex-grow: 0.2;
61 | justify-content: center;
62 | }
63 |
64 | .team-points {
65 | flex-grow: 0.6;
66 | }
67 |
68 | .arrows {
69 | cursor: pointer;
70 | flex-grow: 0.2;
71 | }
72 |
73 | .team-address {
74 | justify-content: flex-start;
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/client/components/Admin/AdminOptions/adminOptions.scss:
--------------------------------------------------------------------------------
1 | .admin-change-phase {
2 | padding: 50px 10px;
3 | text-align: center;
4 | font-size: 20px;
5 |
6 | span {
7 | margin-right: 10px;
8 | }
9 |
10 | button {
11 | display: inline-flex;
12 | align-items: center;
13 | justify-content: center;
14 | color: white;
15 | background: #673AB7;
16 | padding: 10px 20px;
17 | font-size: 16px;
18 | border-radius: 3px;
19 |
20 | &:hover {
21 | background: lighten(#673AB7, 15);
22 | }
23 |
24 | &:disabled {
25 | user-select: none;
26 | cursor: not-allowed;
27 | background: lighten(#673AB7, 30);
28 | }
29 | }
30 |
31 | .change-error {
32 | margin-top: 15px;
33 | font-size: 16px;
34 | color: #F57170;
35 | }
36 | }
--------------------------------------------------------------------------------
/client/components/Admin/AdminSponsors/AdminSponsorsForm.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Field, reduxForm } from 'redux-form';
3 | import { connect } from 'react-redux';
4 | import formGroup from '../../FormComonent/FormComponent';
5 | import { sponsorsFormValidator, submitAddSponsorsForm } from '../../../actions/sponsorActions';
6 | import CubeLoader from '../../Decorative/CubeLoader/CubeLoader';
7 |
8 | require('../../FormComonent/forms.scss');
9 |
10 | let SponsorsTeamsForm = ({ handleSubmit, pristine, submittingForm, invalid, addSponsorError, submitText, submitTextSubmitting }) => ( // eslint-disable-line
11 |
67 | );
68 |
69 | SponsorsTeamsForm.defaultProps = {
70 | submitTextSubmitting: 'Adding',
71 | submitText: 'Add',
72 | };
73 |
74 | const mapStateToProps = (state) => ({
75 | submittingForm: state.sponsors.submitting,
76 | addSponsorError: state.sponsors.addSponsorError
77 | });
78 |
79 | const validate = sponsorsFormValidator;
80 |
81 | SponsorsTeamsForm = reduxForm({ form: 'sponsorsForm', validate })(SponsorsTeamsForm);
82 |
83 | export default connect(mapStateToProps, { onSubmit: submitAddSponsorsForm })(SponsorsTeamsForm);
84 |
--------------------------------------------------------------------------------
/client/components/Admin/AdminSponsors/_index.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DecenterApps/Smart-Hackathon-DApp/31e6419a47c1a6c9885e35b359466c7fa26eb032/client/components/Admin/AdminSponsors/_index.scss
--------------------------------------------------------------------------------
/client/components/Admin/AdminSponsors/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import PropTypes from 'prop-types';
5 | import OpenModalButton from '../../OpenModalButton/OpenModalButton';
6 | import sponsorActions from '../../../actions/sponsorActions';
7 | import Loader from '../../Decorative/Loader/index.jsx';
8 |
9 | class AdminSponsors extends Component {
10 | constructor(props) {
11 | super(props);
12 |
13 | this.state = {};
14 | }
15 | componentWillMount() {
16 | this.props.fetchSponsors();
17 | }
18 | render() {
19 | return (
20 |
21 | {
22 | this.props.sponsors.isFetching &&
23 |
24 |
Loading
25 |
26 | }
27 | {
28 | !this.props.sponsors.isFetching &&
29 | this.props.sponsors.error &&
30 |
31 |
{this.props.sponsors.error.toString()}
32 |
33 | }
34 | {
35 | !this.props.sponsors.isFetching &&
36 | !this.props.sponsors.sponsors.length > 0 &&
37 |
38 |
39 |
No sponsors
40 |
41 |
42 |
43 | }
44 |
45 | {
46 | !this.props.sponsors.isFetching &&
47 | this.props.sponsors.sponsors.length > 0 &&
48 |
65 | }
66 |
67 | );
68 | }
69 | }
70 |
71 | AdminSponsors.propTypes = {
72 | sponsors: PropTypes.shape({
73 | sponsors: PropTypes.array,
74 | isFetching: PropTypes.bool,
75 | error: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
76 | }),
77 | fetchSponsors: PropTypes.func.isRequired,
78 | };
79 | AdminSponsors.defaultProps = {
80 | sponsors: {
81 | sponsors: []
82 | },
83 | isFetching: true
84 | };
85 |
86 | const mapStateToProps = state => state;
87 | const mapDispatchToProps = dispatch => bindActionCreators({
88 | ...sponsorActions
89 | }, dispatch);
90 |
91 | export default connect(
92 | mapStateToProps,
93 | mapDispatchToProps,
94 | )(AdminSponsors);
95 |
96 |
--------------------------------------------------------------------------------
/client/components/Admin/AdminTeams/AdminTeamsForm.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Field, reduxForm } from 'redux-form';
3 | import { connect } from 'react-redux';
4 | import formGroup from '../../FormComonent/FormComponent';
5 | import { teamsFormValidator, submitAddTeamsForm } from '../../../actions/teamActions';
6 | import CubeLoader from '../../Decorative/CubeLoader/CubeLoader';
7 |
8 | require('../../FormComonent/forms.scss');
9 |
10 | let AdminTeamsForm = ({ handleSubmit, pristine, submittingForm, invalid, addTeamError }) => ( // eslint-disable-line
11 |
68 | );
69 |
70 | const mapStateToProps = (state) => ({
71 | submittingForm: state.teams.submitting,
72 | addTeamError: state.teams.addTeamError
73 | });
74 |
75 | const validate = teamsFormValidator;
76 |
77 | AdminTeamsForm = reduxForm({ form: 'teamsForm', validate })(AdminTeamsForm);
78 |
79 | export default connect(mapStateToProps, { onSubmit: submitAddTeamsForm })(AdminTeamsForm);
80 |
--------------------------------------------------------------------------------
/client/components/Admin/AdminTeams/_index.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DecenterApps/Smart-Hackathon-DApp/31e6419a47c1a6c9885e35b359466c7fa26eb032/client/components/Admin/AdminTeams/_index.scss
--------------------------------------------------------------------------------
/client/components/Admin/AdminTeams/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import PropTypes from 'prop-types';
5 | import DollarIcon from '../../Decorative/DollarIcon/index.jsx';
6 | import Loader from '../../Decorative/Loader/index.jsx';
7 | import teamActions from '../../../actions/teamActions';
8 | import OpenModalButton from '../../OpenModalButton/OpenModalButton';
9 |
10 | require('./_index.scss');
11 |
12 | class AdminTeams extends Component {
13 | constructor(props) {
14 | super(props);
15 |
16 | this.state = {};
17 | }
18 | componentWillMount() {
19 | this.props.fetchTeams();
20 | }
21 | render() {
22 | return (
23 |
24 | {
25 | this.props.teams.isFetching &&
26 |
27 |
Loading
28 |
29 | }
30 | {
31 | !this.props.teams.isFetching &&
32 | this.props.teams.error &&
33 |
34 |
{this.props.teams.error.toString()}
35 |
36 | }
37 | {
38 | !this.props.teams.isFetching &&
39 | !this.props.teams.error &&
40 | !this.props.teams.teams.length > 0 &&
41 |
42 |
43 |
No teams
44 |
45 |
46 |
47 | }
48 | {
49 | !this.props.teams.isFetching &&
50 | !this.props.teams.error &&
51 | this.props.teams.teams.length > 0 &&
52 |
53 |
54 | {
55 | this.props.teams.teams.map((team) => (
56 |
57 |
58 | {team.args.teamName}
59 | {team.args.memberNames}
60 |
61 |
62 | {team.args.teamAddress}
63 |
64 |
65 |
66 | ))
67 | }
68 |
69 |
70 | }
71 |
72 | );
73 | }
74 | }
75 |
76 | AdminTeams.propTypes = {
77 | teams: PropTypes.shape({
78 | isFetching: PropTypes.bool.isRequired,
79 | teams: PropTypes.array,
80 | error: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired,
81 | }),
82 | fetchTeams: PropTypes.func.isRequired,
83 | };
84 |
85 | AdminTeams.defaultProps = {
86 | teams: {
87 | isFetching: true,
88 | teams: [1]
89 | }
90 | };
91 |
92 | const mapStateToProps = state => state;
93 | const mapDispatchToProps = dispatch => bindActionCreators({
94 | ...teamActions
95 | }, dispatch);
96 |
97 | export default connect(
98 | mapStateToProps,
99 | mapDispatchToProps,
100 | )(AdminTeams);
101 |
--------------------------------------------------------------------------------
/client/components/Admin/_index.scss:
--------------------------------------------------------------------------------
1 | $highlight: #673AB7;
2 |
3 | .empty-section {
4 | min-height: 150px;
5 | display: flex;
6 | flex-direction: column;
7 | justify-content: center;
8 | text-align: center;
9 |
10 | h1 {
11 | color: #777;
12 | font-size: 21px;
13 | margin: 20px;
14 | font-weight: normal;
15 | }
16 |
17 | button {
18 | border:0;
19 | outline: none;
20 | background-color: $highlight;
21 | color: white;
22 | padding: 10px 20px;
23 | border-radius: 3px;
24 | text-transform: uppercase;
25 | font-size: 14px;
26 | }
27 | }
28 |
29 | .tab-wrapper {
30 | display: flex;
31 | flex-direction: row;
32 | justify-content: space-between;
33 | border-bottom: 1px solid #eee;
34 |
35 | .left-section {
36 | a {
37 | user-select: none;
38 | display: inline-block;
39 | color: #BFBFBF;
40 | text-align: center;
41 | padding: 20px;
42 | margin: 0 10px;
43 | border-bottom: 3px solid white;
44 | cursor: pointer;
45 |
46 | &.active {
47 | color: $highlight;
48 | border-color: $highlight;
49 | }
50 | }
51 | }
52 |
53 | .right-section {
54 | cursor: pointer;
55 | display: flex;
56 | align-items: center;
57 | justify-content: center;
58 | margin-right: 30px;
59 |
60 | button {
61 | color: $highlight;
62 | display: flex;
63 | align-items: center;
64 | justify-content: center;
65 |
66 | svg {
67 | margin-right: 7px;
68 | fill: $highlight;
69 | }
70 |
71 | &:hover {
72 | color: lighten($highlight, 20);
73 | svg {
74 | fill: lighten($highlight, 20);
75 | }
76 | }
77 | }
78 | }
79 | }
80 |
81 | .admin-header {
82 | padding: 20px;
83 | h1 {
84 | color: #777;
85 | font-size: 21px;
86 | margin: 20px;
87 | font-weight: normal;
88 | }
89 |
90 | button {
91 | border:0;
92 | outline: none;
93 | background-color: $highlight;
94 | color: white;
95 | padding: 10px 20px;
96 | border-radius: 3px;
97 | text-transform: uppercase;
98 | font-size: 14px;
99 | }
100 | }
101 |
102 | .display-table {
103 | width: 100%;
104 | text-align: left;
105 |
106 | tr {
107 | border-top: 1px solid #eee;
108 | &:last-child {
109 | border-bottom: 1px solid #eee;
110 | }
111 | }
112 |
113 | th, td {
114 | &:first-child {
115 | padding-left: 30px;
116 | }
117 | &:last-child {
118 | padding-right: 30px;
119 | text-align: right;
120 | }
121 |
122 | padding: 20px 10px;
123 |
124 | &.highlighted {
125 | color: $highlight;
126 | }
127 |
128 | &.rewardable {
129 | width: 40px;
130 | line-height: 10px;
131 | padding-left: 20px;
132 | padding-right: 20px;
133 | }
134 | }
135 | th {
136 | font-weight: normal;
137 | font-size: 16px;
138 |
139 | &.order {
140 | font-size: 14px;
141 | color: #95989A;
142 | font-weight: normal;
143 | }
144 |
145 | }
146 | td {
147 | font-size: 14px;
148 | color: #777;
149 | }
150 |
151 | .clickable {
152 | cursor: pointer;
153 |
154 | &:active{
155 | opacity: 0.8;
156 | }
157 | }
158 | .arrows {
159 | padding: 0px 10px;
160 | display: inline-block;
161 | float: right;
162 |
163 | &:first-of-type {
164 | padding-right: 17px;
165 | }
166 | }
167 | }
--------------------------------------------------------------------------------
/client/components/Admin/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { bindActionCreators } from 'redux';
4 | import { connect } from 'react-redux';
5 | import { Redirect, Link } from 'react-router-dom';
6 | import ModalWrapper from '../Modals/ModalWrapper/ModalWrapper';
7 | import OpenModalButton from '../../components/OpenModalButton/OpenModalButton';
8 | import userActions from '../../actions/userActions';
9 |
10 | require('./_index.scss');
11 |
12 | class Admin extends Component {
13 | constructor(props) {
14 | super(props);
15 |
16 | this.state = {};
17 | }
18 | componentDidMount() {
19 | this.props.checkUser();
20 | }
21 | render() {
22 | return (
23 |
24 | {
25 | this.props.user.isDetermined &&
26 | this.props.user.type === 'administrator' &&
27 |
28 |
29 |
30 |
31 | Teams
32 | Sponsors
33 | Judges
34 | Options
35 |
36 |
37 |
38 | {
39 | (location.hash === '#/admin/' || location.hash === '#/admin' || location.hash === '#/admin/teams') &&
40 | this.props.teams.teams.length > 0 &&
41 |
42 | }
43 | {
44 | (location.hash === '#/admin/sponsors') && this.props.sponsors.sponsors.length > 0 &&
45 |
46 | }
47 | {
48 | (location.hash === '#/admin/judges') && this.props.judges.judges.length > 0 &&
49 |
50 | }
51 |
52 |
53 | { this.props.children }
54 |
55 |
56 |
57 | }
58 |
59 | {
60 | this.props.user.isDetermined &&
61 | this.props.user.type !== 'administrator' &&
62 |
63 | }
64 |
65 | {
66 | this.props.user.isDetermined &&
67 | this.props.user.type === 'administrator' &&
68 | (location.hash === '#/admin/' || location.hash === '#/admin') &&
69 |
70 | }
71 |
72 | );
73 | }
74 | }
75 |
76 | Admin.propTypes = {
77 | teams: PropTypes.object.isRequired,
78 | sponsors: PropTypes.object.isRequired,
79 | judges: PropTypes.object.isRequired,
80 | children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
81 | user: PropTypes.object.isRequired,
82 | checkUser: PropTypes.func.isRequired,
83 | };
84 |
85 |
86 | const mapStateToProps = state => state;
87 | const mapDispatchToProps = dispatch => bindActionCreators({
88 | ...userActions
89 | }, dispatch);
90 |
91 | export default connect(
92 | mapStateToProps,
93 | mapDispatchToProps,
94 | )(Admin);
95 |
--------------------------------------------------------------------------------
/client/components/App/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 | import Footer from '../Footer/Footer';
5 | import AdminHeader from '../Header/HeaderStatus/index';
6 | import AdminSponsorsForm from '../Admin/AdminSponsors/AdminSponsorsForm';
7 | import { checkUser } from '../../actions/userActions';
8 | import Scoreboard from '../Scoreboard/Scoreboard';
9 |
10 | const styles = require('./app.scss');
11 |
12 | class App extends Component {
13 | constructor(props) {
14 | super(props);
15 |
16 | this.state = {};
17 | }
18 |
19 | componentDidMount() {
20 | this.props.$checkUser();
21 | }
22 |
23 | render() {
24 | return (
25 |
26 |
27 |
28 |
29 |
Contribute to prize pool
30 |
31 |
32 |
33 |
34 |
35 | );
36 | }
37 | }
38 |
39 | App.propTypes = {
40 | $checkUser: PropTypes.func.isRequired
41 | };
42 |
43 | export default connect(null, { $checkUser: checkUser })(App);
44 |
--------------------------------------------------------------------------------
/client/components/App/app.scss:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | font-family: "Open Sans", sans-serif;
4 | }
5 |
6 | html {
7 | font-size: 62.5%;
8 | min-height: 100%;
9 | width: 100%;
10 | }
11 |
12 | body {
13 | font-size: 1.6rem;
14 | font-family: Arial, sans-serif;
15 | margin: 0;
16 | padding: 0;
17 | min-height: 100%;
18 | width: 100%;
19 | background: #c05228;
20 | background: linear-gradient(45deg, #c05228 0%, #9F1B22 50%, #562889 100%);
21 | }
22 |
23 | .container {
24 | width: 100%;
25 | max-width: 1200px;
26 | margin: auto;
27 |
28 | &.white {
29 | background-color: white;
30 | border-radius: 3px;
31 | padding: 0;
32 | overflow: hidden;
33 |
34 | .content {
35 | padding: 20px;
36 | }
37 | }
38 |
39 | &.hero {
40 | height: calc(100vh - 200px);
41 | display: flex;
42 | flex-direction: column;
43 | justify-content: center;
44 | text-align: center;
45 |
46 | h1 {
47 | color: #777;
48 | font-size: 36px;
49 | margin: 20px;
50 | font-weight: normal;
51 | }
52 | }
53 | }
54 |
55 | a {
56 | color: inherit;
57 | text-decoration: none;
58 | transition: 0.2s;
59 |
60 | &:focus, &:hover, &:link {
61 | text-decoration: none;
62 | }
63 | };
64 |
65 | canvas {
66 | position: absolute;
67 | top: 0;
68 | right: 0;
69 | bottom: 0;
70 | left: 0;
71 | z-index: 0;
72 | }
73 |
74 | #root {
75 | position: relative;
76 | z-index: 1;
77 | }
78 |
79 | button {
80 | padding: 0;
81 | border: 0;
82 | background: none;
83 | cursor: pointer;
84 |
85 | &:focus {
86 | outline: 0;
87 | }
88 | }
89 | .app {
90 | text-align: center;
91 |
92 | .form-wrapper {
93 | display: inline-block;
94 | max-width: 470px;
95 | background: rgba(0, 0, 0, 0.1);
96 | border-radius: 3px;
97 | margin: 60px 0 40px 0;
98 | padding: 30px;
99 |
100 | form {
101 | width: 100%;
102 | max-width: 390px;
103 | display: inline-block;
104 | }
105 |
106 | .form-item {
107 | background: transparent;
108 | border-radius: 3px;
109 | border: 2px solid #fff;
110 | padding: 16px 20px;
111 | text-align: left;
112 | color: white;
113 |
114 | $gray: #D4D4D4;
115 |
116 | &::-webkit-input-placeholder { /* Chrome/Opera/Safari */
117 | color: $gray;
118 | }
119 | &::-moz-placeholder { /* Firefox 19+ */
120 | color: $gray;
121 | }
122 | &:-ms-input-placeholder { /* IE 10+ */
123 | color: $gray;
124 | }
125 | &:-moz-placeholder { /* Firefox 18- */
126 | color: $gray;
127 | }
128 | }
129 |
130 | .form-item-error {
131 | border-color: #F57170;
132 | outline-color: #F57170;
133 | }
134 |
135 | .submit-button {
136 | background: white;
137 | color: #a72823;
138 | font-weight: 700;
139 |
140 | .sk-cube-grid .sk-cube {
141 | background-color: #FF9800;
142 | }
143 |
144 | &:hover:not(:disabled) {
145 | color: lighten(#FF9800, 15);
146 | }
147 |
148 | &:disabled {
149 | user-select: none;
150 | cursor: not-allowed;
151 | opacity: 0.5;
152 | }
153 | }
154 | }
155 |
156 | .form-name {
157 | color: #fff;
158 | font-size: 34px;
159 | font-weight: 700;
160 | }
161 | }
162 |
163 | .table {
164 | display: flex;
165 | flex-flow: column nowrap;
166 | font-size: 14px;
167 | line-height: 1.5;
168 | flex: 1 1 auto;
169 | color: #777;
170 | }
171 |
172 | .table-info-header {
173 | border-top: 0 !important;
174 | font-weight: bold;
175 | }
176 |
177 | .th {
178 | text-align: left;
179 | }
180 |
181 | .th > .td {
182 | white-space: normal;
183 | justify-content: center;
184 | }
185 |
186 | .tr {
187 | width: 100%;
188 | display: flex;
189 | flex-flow: row nowrap;
190 | border-bottom: 1px solid #eee;
191 | &:first-child {
192 | border-top: 1px solid #eee;
193 | }
194 | }
195 |
196 | .td {
197 | display: flex;
198 | flex-flow: row nowrap;
199 | align-items: center;
200 | flex-grow: 1;
201 | flex-basis: 0;
202 | padding: 20px 5px;
203 | overflow: hidden;
204 | text-overflow: ellipsis;
205 | min-width: 0;
206 | white-space: nowrap;
207 | }
208 |
--------------------------------------------------------------------------------
/client/components/Decorative/AddCircleIcon/AddCircleIcon.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const AddCircleIcon = ({ size }) => (
5 |
6 |
7 |
11 |
12 |
13 | );
14 |
15 | AddCircleIcon.propTypes = {
16 | size: PropTypes.string
17 | };
18 | AddCircleIcon.defaultProps = {
19 | size: '18'
20 | };
21 |
22 | export default AddCircleIcon;
23 |
--------------------------------------------------------------------------------
/client/components/Decorative/CloseIcon/CloseIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const CloseIcon = ({ size }) => (
5 |
16 |
25 |
26 | );
27 |
28 | CloseIcon.propTypes = {
29 | size: PropTypes.string
30 | };
31 | CloseIcon.defaultProps = {
32 | size: '25px'
33 | };
34 |
35 | export default CloseIcon;
36 |
--------------------------------------------------------------------------------
/client/components/Decorative/CubeLoader/CubeLoader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | require('./cubeLoader.scss');
4 |
5 | const CubeLoader = () => (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 |
19 | export default CubeLoader;
20 |
--------------------------------------------------------------------------------
/client/components/Decorative/CubeLoader/cubeLoader.scss:
--------------------------------------------------------------------------------
1 | .sk-cube-grid {
2 | width: 15px;
3 | height: 15px;
4 | margin-right: 15px;
5 | }
6 |
7 | .sk-cube-grid .sk-cube {
8 | width: 33%;
9 | height: 33%;
10 | background-color: white;
11 | float: left;
12 | -webkit-animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;
13 | animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;
14 | }
15 | .sk-cube-grid .sk-cube1 {
16 | -webkit-animation-delay: 0.2s;
17 | animation-delay: 0.2s; }
18 | .sk-cube-grid .sk-cube2 {
19 | -webkit-animation-delay: 0.3s;
20 | animation-delay: 0.3s; }
21 | .sk-cube-grid .sk-cube3 {
22 | -webkit-animation-delay: 0.4s;
23 | animation-delay: 0.4s; }
24 | .sk-cube-grid .sk-cube4 {
25 | -webkit-animation-delay: 0.1s;
26 | animation-delay: 0.1s; }
27 | .sk-cube-grid .sk-cube5 {
28 | -webkit-animation-delay: 0.2s;
29 | animation-delay: 0.2s; }
30 | .sk-cube-grid .sk-cube6 {
31 | -webkit-animation-delay: 0.3s;
32 | animation-delay: 0.3s; }
33 | .sk-cube-grid .sk-cube7 {
34 | -webkit-animation-delay: 0s;
35 | animation-delay: 0s; }
36 | .sk-cube-grid .sk-cube8 {
37 | -webkit-animation-delay: 0.1s;
38 | animation-delay: 0.1s; }
39 | .sk-cube-grid .sk-cube9 {
40 | -webkit-animation-delay: 0.2s;
41 | animation-delay: 0.2s; }
42 |
43 | @-webkit-keyframes sk-cubeGridScaleDelay {
44 | 0%, 70%, 100% {
45 | -webkit-transform: scale3D(1, 1, 1);
46 | transform: scale3D(1, 1, 1);
47 | } 35% {
48 | -webkit-transform: scale3D(0, 0, 1);
49 | transform: scale3D(0, 0, 1);
50 | }
51 | }
52 |
53 | @keyframes sk-cubeGridScaleDelay {
54 | 0%, 70%, 100% {
55 | -webkit-transform: scale3D(1, 1, 1);
56 | transform: scale3D(1, 1, 1);
57 | } 35% {
58 | -webkit-transform: scale3D(0, 0, 1);
59 | transform: scale3D(0, 0, 1);
60 | }
61 | }
--------------------------------------------------------------------------------
/client/components/Decorative/DollarIcon/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const DollarIcon = ({ color, size }) => (
5 |
6 |
15 |
16 |
17 | );
18 |
19 | DollarIcon.propTypes = {
20 | color: PropTypes.string,
21 | size: PropTypes.string
22 | };
23 | DollarIcon.defaultProps = {
24 | color: '#FFC107',
25 | size: '20px'
26 | };
27 |
28 | export default DollarIcon;
29 |
--------------------------------------------------------------------------------
/client/components/Decorative/EthereumIcon/EthereumIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const EthereumIcon = ({ size }) => (
5 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 |
25 | EthereumIcon.propTypes = {
26 | size: PropTypes.string
27 | };
28 | EthereumIcon.defaultProps = {
29 | size: '20px'
30 | };
31 |
32 | export default EthereumIcon;
33 |
--------------------------------------------------------------------------------
/client/components/Decorative/Loader/_index.scss:
--------------------------------------------------------------------------------
1 | .loader {
2 | display: inline-block;
3 | width: 40px;
4 | height: 40px;
5 | background-color: #333;
6 | border-radius: 100%;
7 | vertical-align: bottom;
8 | margin: 0 5px;
9 | animation: sk-scaleout 1.0s infinite ease-in-out;
10 | }
11 |
12 | @-webkit-keyframes sk-scaleout {
13 | 0% { -webkit-transform: scale(0) }
14 | 100% {
15 | -webkit-transform: scale(1.0);
16 | opacity: 0;
17 | }
18 | }
19 |
20 | @keyframes sk-scaleout {
21 | 0% {
22 | -webkit-transform: scale(0);
23 | transform: scale(0);
24 | } 100% {
25 | -webkit-transform: scale(1.0);
26 | transform: scale(1.0);
27 | opacity: 0;
28 | }
29 | }
--------------------------------------------------------------------------------
/client/components/Decorative/Loader/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | require('./_index.scss');
5 |
6 | const Loader = ({ color, size }) => (
7 |
8 | );
9 |
10 | Loader.propTypes = {
11 | color: PropTypes.string,
12 | size: PropTypes.string
13 | };
14 | Loader.defaultProps = {
15 | color: 'black',
16 | size: '30px'
17 | };
18 |
19 | export default Loader;
20 |
--------------------------------------------------------------------------------
/client/components/Decorative/MedalIcon/MedalIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const MedalIcon = ({ size, color }) => (
5 |
6 |
7 |
12 |
13 |
14 | );
15 |
16 | MedalIcon.propTypes = {
17 | size: PropTypes.string,
18 | color: PropTypes.string,
19 | };
20 | MedalIcon.defaultProps = {
21 | size: '22',
22 | color: '#000'
23 | };
24 |
25 | export default MedalIcon;
26 |
--------------------------------------------------------------------------------
/client/components/Decorative/StarIcon/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const StarIcon = ({ color, size }) => (
5 |
6 |
7 |
8 |
13 |
14 |
15 | );
16 |
17 |
18 | StarIcon.propTypes = {
19 | color: PropTypes.string,
20 | size: PropTypes.string
21 | };
22 | StarIcon.defaultProps = {
23 | color: '#FFC107',
24 | size: '20px'
25 | };
26 |
27 | export default StarIcon;
28 |
--------------------------------------------------------------------------------
/client/components/Footer/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | require('./footer.scss');
4 |
5 | const Footer = () => (
6 |
7 | Hackathon organized by Startit
8 | Hackathon DApp developed by Decenter
9 |
10 | );
11 |
12 | export default Footer;
13 |
--------------------------------------------------------------------------------
/client/components/Footer/footer.scss:
--------------------------------------------------------------------------------
1 | .footer{
2 | text-align: center;
3 | bottom: 0;
4 | width: 100%;
5 | color: white;
6 | font-size: 16px;
7 | margin: 50px 0 35px;
8 | a:hover {
9 | color: white;
10 | text-decoration: underline;
11 | }
12 | }
--------------------------------------------------------------------------------
/client/components/FormComonent/FormComponent.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const FormGroup = ({
5 | input, placeholder, wrapperClassName, inputClassName, errorClassName, showErrorText,
6 | type, id, showLabel, labelText, labelClass, meta: { touched, error }
7 | }) => (
8 |
9 |
10 | {showLabel &&
{ labelText } }
11 | {touched && ((error && showErrorText &&
{error}
))}
12 |
13 | );
14 |
15 | FormGroup.defaultProps = {
16 | showLabel: false,
17 | labelText: '',
18 | labelClass: '',
19 | id: '',
20 | placeholder: '',
21 | showErrorText: false
22 | };
23 |
24 | FormGroup.propTypes = {
25 | input: PropTypes.any.isRequired,
26 | placeholder: PropTypes.string,
27 | wrapperClassName: PropTypes.string.isRequired,
28 | inputClassName: PropTypes.string.isRequired,
29 | errorClassName: PropTypes.string.isRequired,
30 | type: PropTypes.string.isRequired,
31 | id: PropTypes.string,
32 | showLabel: PropTypes.bool,
33 | labelText: PropTypes.string,
34 | labelClass: PropTypes.string,
35 | meta: PropTypes.object.isRequired,
36 | showErrorText: PropTypes.bool
37 | };
38 |
39 | export default FormGroup;
40 |
--------------------------------------------------------------------------------
/client/components/FormComonent/forms.scss:
--------------------------------------------------------------------------------
1 | .form-name {
2 | font-size: 24px;
3 | text-align: center;
4 | margin-bottom: 30px;
5 | }
6 |
7 | .form-wrapper {
8 | width: 100%;
9 |
10 | $errorColor: #F57170;
11 |
12 | .form-item-wrapper {
13 | position: relative;
14 | margin-bottom: 35px;
15 |
16 | .form-item {
17 | border: 2px solid #E2E2E2;
18 | border-radius: 3px;
19 | padding: 19px 0 19px 25px;
20 | width: 100%;
21 | font-size: 14px;
22 | }
23 |
24 | .form-item-error {
25 | border-color: $errorColor;
26 | outline-color: $errorColor;
27 | }
28 |
29 | div.form-item-error {
30 | position: absolute;
31 | color: $errorColor;
32 | top: 61px;
33 | }
34 | }
35 |
36 | .form-item-wrapper-checkbox {
37 | margin-bottom: 20px;
38 | display: flex;
39 | align-items: center;
40 |
41 | label {
42 | cursor: pointer;
43 | user-select: none;
44 |
45 | &:hover {
46 | color: lighten(black, 50);
47 | }
48 | }
49 |
50 | .form-item-checkbox {
51 | cursor: pointer;
52 | display: inline-block;
53 | position: relative;
54 | top: -2px;
55 | height: 24px;
56 | width: 24px;
57 | margin-right: 10px;
58 | }
59 | }
60 |
61 | .submit-error {
62 | font-size: 18px;
63 | margin-bottom: 20px;
64 | color: $errorColor;
65 | }
66 |
67 | .submit-button {
68 | display: flex;
69 | align-items: center;
70 | justify-content: center;
71 | width: 100%;
72 | color: white;
73 | background: #673AB7;
74 | padding: 19px 0;
75 | font-size: 16px;
76 | border-radius: 3px;
77 |
78 | &:hover {
79 | background: lighten(#673AB7, 15);
80 | }
81 |
82 | &:disabled {
83 | user-select: none;
84 | cursor: not-allowed;
85 | background: lighten(#673AB7, 30);
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/client/components/Header/HeaderStatus/_index.scss:
--------------------------------------------------------------------------------
1 | .admin-header-period {
2 | display: flex;
3 | align-items: center;
4 | background-color: transparent;
5 | color: white;
6 | font-size: 18px;
7 |
8 | .period {
9 | margin-right: 10px;
10 | }
11 | }
12 |
13 | .user-type-view-link a {
14 | font-family: "Open Sans", sans-serif;
15 | font-weight: 700;
16 | margin-right: 25px;
17 | font-size: 18px;
18 | background: #842e77;
19 | padding: 12px;
20 | color: white;
21 | border-radius: 3px;
22 |
23 | &:hover {
24 | text-decoration: underline;
25 | }
26 | }
--------------------------------------------------------------------------------
/client/components/Header/HeaderStatus/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { Link } from 'react-router-dom';
4 | import PropTypes from 'prop-types';
5 | import { fetchPhase } from '../../../actions/userActions';
6 | import Header from '../index.jsx';
7 | import CubeLoader from '../../Decorative/CubeLoader/CubeLoader';
8 |
9 | require('./_index.scss');
10 |
11 | class AdminHeader extends Component {
12 | constructor(props) {
13 | super(props);
14 |
15 | this.state = {};
16 | }
17 | componentWillMount() {
18 | this.props.$fetchPhase();
19 | }
20 |
21 | render() {
22 | return (
23 |
24 |
25 |
26 | {
27 | this.props.isDetermined &&
28 | this.props.userType === 'administrator' &&
29 | (location.hash.indexOf('#/admin') === -1) &&
30 | Administrator view
31 | }
32 | {
33 | this.props.isDetermined &&
34 | this.props.userType === 'jury' &&
35 | (location.hash.indexOf('#/jury') === -1) &&
36 | Vote
37 | }
38 |
39 |
40 |
41 | Period:
42 | { this.props.isFetching && }
43 |
44 | {
45 | !this.props.isFetching && !this.props.phaseError &&
46 | {this.props.phases[this.props.phase]}
47 | }
48 |
49 | {
50 | !this.props.isFetching &&
51 | this.props.phaseError &&
52 | {this.props.phaseError}
53 | }
54 |
55 |
56 |
57 | );
58 | }
59 | }
60 |
61 | AdminHeader.propTypes = {
62 | isDetermined: PropTypes.bool.isRequired,
63 | userType: PropTypes.string.isRequired,
64 | phase: PropTypes.number.isRequired,
65 | $fetchPhase: PropTypes.func.isRequired,
66 | isFetching: PropTypes.bool.isRequired,
67 | phaseError: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired,
68 | phases: PropTypes.array.isRequired
69 | };
70 |
71 | const mapStateToProps = (state) => ({
72 | isDetermined: state.user.isDetermined,
73 | userType: state.user.type,
74 | phase: state.user.phase,
75 | isFetching: state.user.isFetching,
76 | phaseError: state.user.phaseError,
77 | phases: state.user.phases
78 | });
79 |
80 | export default connect(mapStateToProps, { $fetchPhase: fetchPhase })(AdminHeader);
81 |
82 |
--------------------------------------------------------------------------------
/client/components/Header/_index.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | width: 100%;
3 | max-width: 1200px;
4 | margin: auto;
5 | padding: 30px 0;
6 | display: flex;
7 | justify-content: space-between;
8 |
9 | .title {
10 | text-align: left;
11 |
12 | h1 {
13 | color: white;
14 | line-height: 30px;
15 | font-size: 26px;
16 | margin: 0;
17 | a {
18 | transition: all .1;
19 | }
20 | &:hover a {
21 | color: white;
22 | opacity: .8;
23 | }
24 | }
25 | h2 {
26 | color: white;
27 | opacity: .7;
28 | font-size: 16px;
29 | margin: 0;
30 | font-weight: normal;
31 | }
32 | }
33 |
34 | .other {
35 | display: flex;
36 | align-items: center;
37 | justify-content: center;
38 | }
39 | }
--------------------------------------------------------------------------------
/client/components/Header/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Link } from 'react-router-dom';
4 |
5 | require('./_index.scss');
6 |
7 | const Header = ({ children, noLink }) => (
8 |
9 |
10 |
11 | {noLink && Blockchain Community Hackathon
}
12 | {!noLink && Blockchain Community Hackathon}
13 |
14 |
Belgrade 25/26/27 August 2017
15 |
16 |
17 | {children}
18 |
19 |
20 | );
21 |
22 | Header.propTypes = {
23 | children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
24 | noLink: PropTypes.bool
25 | };
26 |
27 | Header.defaultProps = {
28 | noLink: false,
29 | children: []
30 | };
31 |
32 | export default Header;
33 |
--------------------------------------------------------------------------------
/client/components/Jury/_index.scss:
--------------------------------------------------------------------------------
1 | .table-header{
2 | padding: 26px 21px 10px 21px;
3 | display: flex;
4 |
5 | &.jury-table-header {
6 | display: flex;
7 | justify-content: space-between;
8 | }
9 |
10 | .button-status-wrapper {
11 | display: flex;
12 |
13 | .voting-status {
14 | margin-right: 15px;
15 | margin-top: 5px;
16 |
17 | &.voting-error {
18 | color: #F57170;
19 | }
20 |
21 | &.voting-success {
22 | color: #28CC9E;
23 | }
24 | }
25 | }
26 |
27 | .title{
28 | font-size: 20px;
29 | color: #212121;
30 | margin-bottom: 8px;
31 | line-height: 20px;
32 | }
33 |
34 | .subtitle {
35 | color: #757575;
36 | font-size: 14px;
37 | line-height: 14px;
38 | }
39 |
40 | .submit-button{
41 | margin-left: auto;
42 | width: 100px;
43 | height: 38px;
44 | background-color: #673AB7;
45 | border-radius: 2px;
46 | font-size: 13px;
47 | color: #fff;
48 |
49 | &:hover:not(:disabled) {
50 | background: lighten(#673AB7, 15);
51 | }
52 |
53 | &:disabled {
54 | user-select: none;
55 | cursor: not-allowed;
56 | background: lighten(#673AB7, 30);
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/client/components/Jury/assets/001-arrows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DecenterApps/Smart-Hackathon-DApp/31e6419a47c1a6c9885e35b359466c7fa26eb032/client/components/Jury/assets/001-arrows.png
--------------------------------------------------------------------------------
/client/components/Jury/assets/002-arrows-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DecenterApps/Smart-Hackathon-DApp/31e6419a47c1a6c9885e35b359466c7fa26eb032/client/components/Jury/assets/002-arrows-1.png
--------------------------------------------------------------------------------
/client/components/Jury/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Redirect } from 'react-router-dom';
3 | import PropTypes from 'prop-types';
4 | import { bindActionCreators } from 'redux';
5 | import { connect } from 'react-redux';
6 |
7 | import * as teamActions from '../../actions/teamActions';
8 | import userActions from '../../actions/userActions';
9 |
10 | import Loader from '../Decorative/Loader/';
11 | import arrowup from './assets/001-arrows.png';
12 | import arrowdown from './assets/002-arrows-1.png';
13 |
14 | require('./_index.scss');
15 |
16 | class Jury extends Component {
17 | constructor(props) {
18 | super(props);
19 |
20 | this.state = {};
21 |
22 | this.voteForTeams = this.voteForTeams.bind(this);
23 | }
24 |
25 | componentDidMount() {
26 | this.props.checkUser();
27 | this.props.fetchTeams();
28 | this.props.checkIfJuryVoted();
29 | }
30 |
31 | voteForTeams() {
32 | const votes = this.props.teams.teams.map((elem) => elem.args.teamAddress);
33 |
34 | console.log(votes);
35 |
36 | this.props.vote(votes);
37 | }
38 |
39 | render() {
40 | return (
41 |
42 | {
43 | this.props.user.isDetermined &&
44 | this.props.user.type === 'jury' &&
45 |
46 |
47 |
48 |
49 |
Cast Your Votes
50 |
Arrange the teams in desired order.
51 |
52 |
53 | {
54 | this.props.user.votingError &&
55 | {this.props.user.votingError}
56 | }
57 | {
58 | this.props.user.votingSuccess &&
59 | Votes sent to contract
60 | }
61 | SUBMIT
66 |
67 |
68 | {
69 | this.props.teams.isFetching &&
70 |
71 |
Loading
72 |
73 | }
74 | {
75 | !this.props.teams.isFetching &&
76 | this.props.teams.error &&
77 |
78 |
{this.props.teams.error.toString()}
79 |
80 | }
81 | {
82 | !this.props.teams.isFetching &&
83 | !this.props.teams.error &&
84 | !this.props.teams.teams.length > 0 &&
85 |
86 |
87 |
No teams
88 |
89 |
90 | }
91 |
92 | {
93 | this.props.teams &&
94 |
95 |
96 | {
97 | this.props.teams.teams.map((team, i) => (
98 |
99 | {i + 1}.
100 | {team.args.teamName}
101 | {team.args.memberNames}
102 |
103 | this.props.moveTeamDown(i)}
106 | role="button"
107 | tabIndex="-1"
108 | >
109 |
110 |
111 | this.props.moveTeamUp(i)}
114 | role="button"
115 | tabIndex="-1"
116 | >
117 |
118 |
119 |
120 |
121 | ))
122 | }
123 |
124 |
125 | }
126 |
127 |
128 | }
129 |
130 | {
131 | this.props.user.isDetermined &&
132 | this.props.user.type !== 'jury' &&
133 |
134 | }
135 |
136 |
137 | );
138 | }
139 | }
140 |
141 | Jury.propTypes = {
142 | teams: PropTypes.shape({
143 | isFetching: PropTypes.bool.isRequired,
144 | teams: PropTypes.array,
145 | error: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
146 | }),
147 | moveTeamUp: PropTypes.func.isRequired,
148 | moveTeamDown: PropTypes.func.isRequired,
149 | vote: PropTypes.func.isRequired,
150 | user: PropTypes.object.isRequired,
151 | checkUser: PropTypes.func.isRequired,
152 | fetchTeams: PropTypes.func.isRequired,
153 | alreadyVoted: PropTypes.bool.isRequired,
154 | voting: PropTypes.bool.isRequired,
155 | votingError: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired,
156 | votingSuccess: PropTypes.bool.isRequired,
157 | checkIfJuryVoted: PropTypes.func.isRequired
158 | };
159 |
160 | Jury.defaultProps = {
161 | alreadyVoted: false,
162 | votingError: false,
163 | voting: false,
164 | votingSuccess: false,
165 | teams: {
166 | isFetching: true,
167 | teams: [1]
168 | }
169 | };
170 |
171 | const mapStateToProps = state => state;
172 | const mapDispatchToProps = dispatch => bindActionCreators({
173 | ...teamActions,
174 | ...userActions
175 | }, dispatch);
176 |
177 | export default connect(
178 | mapStateToProps,
179 | mapDispatchToProps,
180 | )(Jury);
181 |
182 |
--------------------------------------------------------------------------------
/client/components/Landing/_index.scss:
--------------------------------------------------------------------------------
1 | .top-level-wrapper {
2 | overflow: hidden;
3 | max-width: 1200px;
4 | margin: auto;
5 | padding: 0 30px;
6 | }
7 |
8 | .landing-wrapper {
9 | text-align: center;
10 | padding: 40px 0 !important;
11 | min-height: 300px;
12 | display: flex;
13 | flex-direction: column;
14 | justify-content: center;
15 |
16 | .end-wrapper {
17 | .table-wrapper {
18 | margin-top: 30px;
19 |
20 | .scoreboard-header {
21 | margin-bottom: 30px;
22 | }
23 |
24 | .table {
25 | margin-bottom: 0;
26 |
27 | .members {
28 | text-align: left;
29 | }
30 |
31 | .medal {
32 | flex-grow: 0.2;
33 | margin-right: 28px;
34 | }
35 |
36 | .last-child {
37 | //border-bottom: 0;
38 | }
39 | }
40 | }
41 | }
42 |
43 | &.last-phase {
44 | padding-bottom: 0 !important;
45 | overflow: visible;
46 | }
47 |
48 | &.chrome-metamask-wrapper {
49 | .browser-wrapper {
50 | h2 {
51 | margin-bottom: 0;
52 | }
53 |
54 | a {
55 | text-decoration: underline;
56 | margin: 0 5px;
57 | }
58 |
59 | &.not-chrome h2 {
60 | line-height: 35px;
61 | }
62 | }
63 | }
64 |
65 | p {
66 | margin: 0;
67 | a {
68 | text-decoration: underline;
69 | }
70 | }
71 |
72 | .sponsors-wrapper {
73 | margin: 50px 50px 30px;
74 |
75 | .prize-pool-wrapper {
76 | margin-top: 20px;
77 | }
78 |
79 | .sponsor-wrapper {
80 | display: inline-block;
81 | margin: 0 10px 10px;
82 |
83 | .logo {
84 | display: block !important;
85 | margin: 0 auto;
86 | height: 60px;
87 | width: 100px;
88 | background-size: contain !important;
89 | background-position: 50% !important;
90 | background-repeat: no-repeat !important;
91 | }
92 | }
93 | }
94 |
95 | .table-wrapper {
96 | margin: 50px 0 0;
97 | }
98 |
99 | .td.rewardable {
100 | max-width: 60px;
101 | padding-left: 20px;
102 | padding-right: 20px;
103 | }
104 |
105 | h2 {
106 | margin-bottom: 20px;
107 | }
108 | }
--------------------------------------------------------------------------------
/client/components/Landing/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import PropTypes from 'prop-types';
5 | import { Link } from 'react-router-dom';
6 | import userActions from '../../actions/userActions';
7 | import teamActions from '../../actions/teamActions';
8 | import sponsorActions from '../../actions/sponsorActions';
9 |
10 | import Scoreboard from '../Scoreboard/Scoreboard';
11 | import DollarIcon from '../Decorative/DollarIcon/index.jsx';
12 |
13 | require('./_index.scss');
14 |
15 | class Landing extends Component {
16 | constructor(props) {
17 | super(props);
18 |
19 | this.state = {};
20 | }
21 | componentWillMount() {
22 | this.props.checkUser();
23 | this.props.fetchSponsors();
24 | this.props.fetchPrizePoolSize();
25 | if(this.props.user.phase === 0) {
26 | this.props.fetchTeams();
27 | }
28 | if(this.props.user.phase === 4) {
29 | this.props.fetchTeamScores();
30 | }
31 | }
32 | componentWillReceiveProps(nextProps) {
33 | if(nextProps.user.phase !== this.props.user.phase) {
34 | console.log(nextProps.user.phase, this.props.user.phase);
35 | if(nextProps.user.phase === 4) {
36 | this.props.fetchTeamScores();
37 | } else {
38 | this.props.fetchTeams();
39 | }
40 | }
41 | }
42 | render() {
43 | return (
44 |
45 |
46 | {
47 | this.props.user.phase === 0 &&
48 |
49 |
Welcome to Blockchain Community Hackathon
50 |
51 | The competition has not started yet.
52 | If you are a competitor, please make sure your details below are correct,
53 | especially Ethereum address!
54 |
55 | You can also contribute to the prize pool.
56 |
57 |
58 | }
59 | {
60 | this.props.user.phase === 1 &&
61 |
62 |
Competition is ongoing.
63 |
64 | Feel free to contribute to the prize pool.
65 |
66 |
67 | }
68 | {
69 | (this.props.user.phase === 2 ||
70 | this.props.user.phase === 3) &&
71 |
72 |
The judges are voting, please be patient.
73 |
74 | Feel free to contribute to the prize pool.
75 |
76 |
77 | }
78 | {
79 | this.props.user.phase === 4 &&
80 |
81 |
Thanks for being a part of Blockchain Community Hackathon!
82 |
83 |
84 |
Here is the final scoreboard:
85 |
86 |
87 |
88 | }
89 | {
90 | this.props.sponsors.sponsors.length > 0 &&
91 |
92 |
Sponsors:
93 | {
94 | this.props.sponsors.sponsors.map((sponsor) => (
95 |
112 | ))
113 | }
114 | {
115 | this.props.sponsors.ethPrize &&
116 |
117 | Total prize pool size:
118 | { this.props.sponsors.ethPrize } ETH
119 | {
120 | this.props.sponsors.eurPrize &&
121 | ({ this.props.sponsors.eurPrize } EUR)
122 | }
123 |
124 |
125 | }
126 |
127 | }
128 | {
129 | (this.props.user.phase === 0 ||
130 | this.props.user.phase === 1 ||
131 | this.props.user.phase === 2 ||
132 | this.props.user.phase === 3) &&
133 | this.props.teams.teams.length > 0 &&
134 |
135 |
Teams:
136 |
137 |
138 | {
139 | this.props.teams.teams.filter(team => !team.args.disqualified).map((team) => (
140 |
141 |
142 |
143 |
144 | {team.args.teamName}
145 | {team.args.memberNames}
146 |
147 |
148 | {team.args.teamAddress}
149 |
150 |
151 |
152 | ))
153 | }
154 |
155 |
156 |
157 | }
158 |
159 |
160 | );
161 | }
162 | }
163 |
164 | Landing.propTypes = {
165 | user: PropTypes.object.isRequired,
166 | teams: PropTypes.shape({
167 | teams: PropTypes.array,
168 | }),
169 | sponsors: PropTypes.shape({
170 | sponsors: PropTypes.array,
171 | ethPrize: PropTypes.string,
172 | eurPrize: PropTypes.string,
173 | }),
174 | checkUser: PropTypes.func.isRequired,
175 | fetchTeams: PropTypes.func.isRequired,
176 | fetchTeamScores: PropTypes.func.isRequired,
177 | fetchSponsors: PropTypes.func.isRequired,
178 | fetchPrizePoolSize: PropTypes.func.isRequired,
179 | };
180 |
181 | Landing.defaultProps = {
182 | teams: {
183 | teams: []
184 | },
185 | sponsors: {
186 | sponsors: []
187 | },
188 | };
189 |
190 | const mapStateToProps = state => state;
191 | const mapDispatchToProps = dispatch => bindActionCreators({
192 | ...userActions,
193 | ...teamActions,
194 | ...sponsorActions,
195 | }, dispatch);
196 |
197 | export default connect(
198 | mapStateToProps,
199 | mapDispatchToProps,
200 | )(Landing);
201 |
202 |
--------------------------------------------------------------------------------
/client/components/Modals/JudgeModal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AdminJudgesForm from '../Admin/AdminJudges/AdminJudgesForm';
3 |
4 | const JudgeModal = () => (
5 |
9 | );
10 |
11 | export default JudgeModal;
12 |
--------------------------------------------------------------------------------
/client/components/Modals/ModalWrapper/ModalWrapper.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 | import { Modal } from 'react-modal-bootstrap';
5 | import toggleModal from '../../../actions/modalsActions';
6 | import CloseIcon from '../../Decorative/CloseIcon/CloseIcon';
7 |
8 | require('./modalWrapper.scss');
9 |
10 | const ModalWrapper = ({ isOpen, $toggleModal, currentModal, $style }) => (
11 |
12 |
13 | $toggleModal(location.hash, false)}
17 | >
18 |
19 |
20 |
21 |
22 | { currentModal() }
23 |
24 |
25 | );
26 |
27 | ModalWrapper.propTypes = {
28 | isOpen: PropTypes.bool.isRequired,
29 | $toggleModal: PropTypes.func.isRequired,
30 | currentModal: PropTypes.func.isRequired,
31 | $style: PropTypes.object.isRequired
32 | };
33 |
34 | const mapStateToProps = (state) => ({
35 | isOpen: state.modals.currentOpen,
36 | currentModal: state.modals.currentModal,
37 | $style: { open: { top: 95 } }
38 | });
39 |
40 | export default connect(mapStateToProps, { $toggleModal: toggleModal })(ModalWrapper);
41 |
--------------------------------------------------------------------------------
/client/components/Modals/ModalWrapper/modalWrapper.scss:
--------------------------------------------------------------------------------
1 | .modal-header {
2 | justify-content: flex-end;
3 | border-bottom: 0;
4 |
5 | span {
6 | display: flex;
7 | cursor: pointer;
8 | margin: 5px 10px 0 0;
9 | }
10 | }
11 |
12 | .modal-wrapper-body {
13 | padding: 6px 60px 60px 60px;
14 |
15 | display: flex;
16 | justify-content: center;
17 | }
--------------------------------------------------------------------------------
/client/components/Modals/SponsorModal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AdminSponsorsForm from '../Admin/AdminSponsors/AdminSponsorsForm';
3 |
4 | const SponsorModal = () => (
5 |
6 |
Add a sponsor
7 |
8 |
9 | );
10 |
11 | export default SponsorModal;
12 |
--------------------------------------------------------------------------------
/client/components/Modals/TeamModal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AdminTeamsForm from '../Admin/AdminTeams/AdminTeamsForm';
3 |
4 | const TeamModal = () => (
5 |
9 | );
10 |
11 | export default TeamModal;
12 |
--------------------------------------------------------------------------------
/client/components/NoMetamask/NoMetamask.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Header from '../Header/index';
4 |
5 | const NoMetamask = ({ isChrome }) => (
6 |
7 |
8 |
9 |
10 | {
11 | isChrome &&
12 |
13 |
14 | In order to use this app you need to install the
15 | Metamask
16 | extension.
17 |
18 |
19 | }
20 | {
21 | !isChrome &&
22 |
23 |
24 | In order to use this app you must access it
25 | through the Chrome web browser
26 |
27 | and install the
28 | Metamask
29 | extension.
30 |
31 |
32 | }
33 |
34 |
35 | );
36 |
37 | NoMetamask.propTypes = {
38 | isChrome: PropTypes.bool.isRequired
39 | };
40 |
41 | export default NoMetamask;
42 |
--------------------------------------------------------------------------------
/client/components/OpenModalButton/OpenModalButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import toggleModal from '../../actions/modalsActions';
5 | import AddCircleIcon from '../Decorative/AddCircleIcon/AddCircleIcon';
6 |
7 | const OpenModalButton = ({ text, $toggleModal, showIcon, phase }) => (
8 |
9 | {
10 | (phase === 0) &&
11 | $toggleModal(location.hash, true)}>
12 | { showIcon && }
13 | {text}
14 |
15 | }
16 |
17 | );
18 |
19 | OpenModalButton.defaultProps = {
20 | showIcon: false
21 | };
22 |
23 | OpenModalButton.propTypes = {
24 | $toggleModal: PropTypes.func.isRequired,
25 | text: PropTypes.string.isRequired,
26 | showIcon: PropTypes.bool,
27 | phase: PropTypes.number.isRequired
28 | };
29 |
30 | const mapStateToProps = (state) => ({
31 | phase: state.user.phase
32 | });
33 |
34 | export default connect(mapStateToProps, { $toggleModal: toggleModal })(OpenModalButton);
35 |
--------------------------------------------------------------------------------
/client/components/Scoreboard/Scoreboard.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 | import Loader from '../Decorative/Loader/index';
5 | import { fetchTeamScores } from '../../actions/teamActions';
6 | import MedalIcon from '../Decorative/MedalIcon/MedalIcon';
7 |
8 | require('./scoreboard.scss');
9 |
10 | let prizeIndex = 0;
11 |
12 | class Scoreboard extends Component {
13 | constructor(props) {
14 | super(props);
15 |
16 | this.state = {};
17 |
18 | this.renderReward = this.renderReward.bind(this);
19 | }
20 |
21 | renderReward(item) {
22 | // resets prizeIndex on re-rendering
23 | if(prizeIndex + 1 === this.props.teams.length) {
24 | prizeIndex = 0;
25 | }
26 |
27 | if (!item.args.rewardEligible) {
28 | return (
29 | 'N/A'
30 | );
31 | }
32 |
33 | prizeIndex++;
34 |
35 | return (
36 | (
37 | parseFloat(this.props.sponsors.ethPrize)
38 | / (2 ** (prizeIndex))
39 | ).toString() + ' ETH'
40 | );
41 | }
42 |
43 | render() {
44 | return (
45 |
46 |
47 | {
48 | !this.props.isFetching &&
49 | this.props.teamsFetchError &&
50 | !this.props.teams.length > 0 &&
51 |
52 | Error occurred
53 |
54 | }
55 |
56 | {
57 | this.props.isFetching &&
58 |
59 |
Loading
60 |
61 | }
62 |
63 | {
64 | !this.props.isFetching &&
65 | !this.props.teamsFetchError &&
66 | this.props.teams.length > 0 &&
67 |
68 |
69 |
Rank
70 |
71 |
Team
72 |
Members
73 |
Points
74 |
Reward
75 |
76 |
77 | {
78 | this.props.teams.filter(team => !team.args.disqualified).map((team, index) => (
79 |
83 |
#{index + 1}
84 |
85 |
86 | {index === 0 && }
87 | {index === 1 && }
88 | {index === 2 && }
89 |
90 |
91 |
92 | {team.args.teamName}
93 |
94 |
95 |
{team.args.memberNames}
96 |
97 |
100 |
{team.args.totalScore}
101 | {
102 | team.args.scoreBreakdown &&
103 |
107 | {team.args.scoreBreakdown.map((juryVote) => (
108 |
109 | {juryVote.juryMemberName}:
110 | {juryVote.points}
111 |
112 | ))}
113 |
114 | }
115 |
116 |
117 |
118 |
119 | {this.renderReward(team)}
120 |
121 |
122 |
123 | ))
124 | }
125 |
126 | }
127 |
128 |
129 | );
130 | }
131 | }
132 |
133 | Scoreboard.propTypes = {
134 | teams: PropTypes.array.isRequired,
135 | isFetching: PropTypes.bool.isRequired,
136 | teamsFetchError: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired,
137 | sponsors: PropTypes.object.isRequired,
138 | };
139 |
140 | const mapStateToProps = (state) => ({
141 | teams: state.teams.teams,
142 | isFetching: state.teams.isFetching,
143 | teamsFetchError: state.teams.error,
144 | sponsors: state.sponsors,
145 | });
146 |
147 | export default connect(mapStateToProps, {})(Scoreboard);
148 |
--------------------------------------------------------------------------------
/client/components/Scoreboard/scoreboard.scss:
--------------------------------------------------------------------------------
1 | .scoreboard {
2 | .table {
3 | color: black !important;
4 | }
5 |
6 | .container.white {
7 | padding: 0;
8 | overflow: visible;
9 | }
10 |
11 | .container-title {
12 | padding: 30px;
13 | font-size: 20px;
14 | border-bottom: 1px solid #dadada;
15 | }
16 |
17 | .scoreboard-regular-header {
18 | padding: 30px;
19 | font-size: 20px;
20 | text-align: left;
21 | border-top: 1px solid #dadada;
22 | }
23 |
24 | .teams {
25 | .team {
26 |
27 | .team-rank {
28 | font-size: 14px;
29 | justify-content: center;
30 | flex-grow: 0.4;
31 | }
32 |
33 | .medal, .reward-amount, .total-points {
34 | font-size: 14px;
35 | flex-grow: 0.4;
36 | justify-content: center;
37 | }
38 |
39 | .team-name-wrapper {
40 | .rewardable {
41 | position: relative;
42 | top: 3px;
43 | margin-right: 10px;
44 | }
45 |
46 | .team-name {
47 | font-weight: 500;
48 | font-size: 14px;
49 | white-space: nowrap;
50 | overflow: hidden;
51 | text-overflow: ellipsis;
52 | }
53 | }
54 |
55 | .total-points {
56 | align-items: center;
57 | justify-content: center;
58 | display: flex;
59 | flex-grow: 0.3;
60 | overflow: visible;
61 | cursor: pointer;
62 | }
63 |
64 | .members {
65 | flex-grow: 2;
66 | display: block;
67 | line-height: 27px;
68 | }
69 |
70 | .reward-amount {
71 | padding-right: 20px;
72 | line-height: 25px;
73 | flex-grow: 0.4;
74 | text-align: right;
75 | display: flex;
76 | align-items: center;
77 | justify-content: flex-end;
78 |
79 | .reward-amount-eth {
80 | white-space: nowrap;
81 | overflow: hidden;
82 | text-overflow: ellipsis;
83 | }
84 |
85 | .eth-icon-wrapper {
86 | margin-left: 7px;
87 | display: flex;
88 | align-items: center;
89 | justify-content: flex-end;
90 | }
91 | }
92 | }
93 |
94 | &:last-child {
95 | .team .td {
96 | //border-bottom: 0;
97 | }
98 | }
99 | }
100 | }
101 |
102 | div.tooltips {
103 | position: relative;
104 | display: inline;
105 | }
106 | div.tooltips .tooltip-wrapper {
107 | position: absolute;
108 | width: 250px;
109 | color: #FFFFFF;
110 | background: #673AB7;
111 | top: 49px;
112 | line-height: 25px;
113 | text-align: center;
114 | visibility: hidden;
115 | border-radius: 3px;
116 |
117 | .jury-row {
118 | display: flex;
119 | align-items: center;
120 | justify-content: space-between;
121 | font-size: 12px;
122 | padding: 3px 8px;
123 |
124 | .jury-name {
125 | max-width: 185px;
126 | white-space: nowrap;
127 | overflow: hidden;
128 | text-overflow: ellipsis;
129 | }
130 | }
131 | }
132 | div.tooltips .tooltip-wrapper:after {
133 | content: '';
134 | position: absolute;
135 | top: -8px;
136 | transform: rotate(180deg);
137 | margin-left: -8px;
138 | width: 0; height: 0;
139 | border-top: 8px solid #673AB7;
140 | border-right: 8px solid transparent;
141 | border-left: 8px solid transparent;
142 | right: 19px;
143 | }
144 | div:hover.tooltips .tooltip-wrapper {
145 | visibility: visible;
146 | opacity: 0.9;
147 | bottom: 30px;
148 | right: 10%;
149 | z-index: 999;
150 | }
151 |
--------------------------------------------------------------------------------
/client/components/Sponsor/_index.scss:
--------------------------------------------------------------------------------
1 | .table-header{
2 | display: flex;
3 |
4 | .title{
5 | font-size: 20px;
6 | color: #212121;
7 | margin-bottom: 8px;
8 | line-height: 20px;
9 | }
10 |
11 | .subtitle {
12 | color: #757575;
13 | font-size: 14px;
14 | line-height: 14px;
15 | }
16 |
17 | .submit-button{
18 | margin-left: auto;
19 | width: 100px;
20 | height: 38px;
21 | background-color: #673AB7;
22 | border-radius: 2px;
23 | font-size: 13px;
24 | color: #fff;
25 |
26 | &:hover:not(:disabled) {
27 | background: lighten(#673AB7, 15);
28 | }
29 |
30 | &:disabled {
31 | user-select: none;
32 | cursor: not-allowed;
33 | background: lighten(#673AB7, 30);
34 | }
35 | }
36 | }
37 |
38 |
39 |
40 | .sponsor-thanks {
41 | font-size: 18px;
42 | margin-bottom: 20px;
43 | background-color: #43b743;
44 | padding: 20px;
45 | color: white;
46 | border-radius: 3px;
47 | }
--------------------------------------------------------------------------------
/client/components/Sponsor/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Redirect } from 'react-router-dom';
3 |
4 | import PropTypes from 'prop-types';
5 | import { bindActionCreators } from 'redux';
6 | import { connect } from 'react-redux';
7 | import * as userActions from '../../actions/userActions';
8 | import AdminSponsorsForm from '../Admin/AdminSponsors/AdminSponsorsForm';
9 |
10 | require('./_index.scss');
11 |
12 | class Sponsor extends Component {
13 | constructor(props) {
14 | super(props);
15 |
16 | this.state = {};
17 | }
18 |
19 | componentDidMount() {
20 | this.props.checkUser();
21 | }
22 |
23 | render() {
24 | return (
25 |
26 | {
27 | this.props.user.phase > 2 &&
28 |
29 | }
30 |
31 | {
32 | this.props.sponsors.showThanks &&
33 |
Thanks for contributing!
34 | }
35 |
36 | {
37 | !this.props.sponsors.showThanks &&
38 |
39 |
Contribute to prize pool
40 |
41 |
42 | }
43 |
44 | );
45 | }
46 | }
47 |
48 | Sponsor.propTypes = {
49 | checkUser: PropTypes.func.isRequired,
50 | user: PropTypes.shape({
51 | phase: PropTypes.number.isRequired
52 | }).isRequired,
53 | sponsors: PropTypes.object.isRequired
54 | };
55 | const mapStateToProps = state => state;
56 |
57 | const mapDispatchToProps = dispatch => bindActionCreators({
58 | ...userActions
59 | }, dispatch);
60 |
61 | export default connect(
62 | mapStateToProps,
63 | mapDispatchToProps,
64 | )(Sponsor);
65 |
--------------------------------------------------------------------------------
/client/config/Routes.jsx:
--------------------------------------------------------------------------------
1 | import { Provider } from 'react-redux';
2 | import { HashRouter, Route, Switch } from 'react-router-dom';
3 | import React, { Component } from 'react';
4 | import PropTypes from 'prop-types';
5 | import App from '../components/App/App.jsx';
6 | import Jury from '../components/Jury/index.jsx';
7 | import Sponsor from '../components/Sponsor/index.jsx';
8 | import Admin from '../components/Admin/index.jsx';
9 | import AdminTeams from '../components/Admin/AdminTeams/index.jsx';
10 | import AdminSponsors from '../components/Admin/AdminSponsors/index.jsx';
11 | import AdminJudges from '../components/Admin/AdminJudges/index.jsx';
12 | import Landing from '../components/Landing/index.jsx';
13 | import HeaderStatus from '../components/Header/HeaderStatus/index';
14 | import Footer from '../components/Footer/Footer';
15 | import AdminOptions from '../components/Admin/AdminOptions/AdminOptions';
16 |
17 | import { periodChangedListener } from '../actions/userActions';
18 | import { judgeEventListener } from '../actions/judgeActions';
19 | import { teamsEventListener } from '../actions/teamActions';
20 | import { sponsorEventListener } from '../actions/sponsorActions';
21 |
22 | class Routes extends Component {
23 | componentWillMount() {
24 | this.props.store.dispatch(periodChangedListener());
25 | this.props.store.dispatch(judgeEventListener());
26 | this.props.store.dispatch(teamsEventListener());
27 | this.props.store.dispatch(sponsorEventListener());
28 | }
29 |
30 | render() {
31 | return (
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 | Routes.propTypes = {
57 | store: PropTypes.object.isRequired,
58 | };
59 |
60 | export default Routes;
61 |
--------------------------------------------------------------------------------
/client/config/initWeb3.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | export default () => {
3 | if (typeof web3 !== 'undefined') {
4 | window.web3 = new Web3(web3.currentProvider);
5 | return true;
6 | } else {
7 | return false;
8 | }
9 |
10 | /*const web3 = require('web3');
11 |
12 | if (!new Web3.providers.HttpProvider('http://localhost:8545')) {
13 | alert('Unable to connect to Web3 provider'); // make a page that says that it can't be used
14 | }
15 |
16 | window.web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));*/
17 | };
18 |
--------------------------------------------------------------------------------
/client/config/storeGenerator.js:
--------------------------------------------------------------------------------
1 | import thunk from 'redux-thunk';
2 | import { createStore, applyMiddleware } from 'redux';
3 | import { hashHistory } from 'react-router';
4 | import { syncHistoryWithStore } from 'react-router-redux';
5 | import reducers from '../reducers/index';
6 |
7 |
8 | const reduxDevToolsEnchancer = window.__REDUX_DEVTOOLS_EXTENSION__ &&
9 | window.__REDUX_DEVTOOLS_EXTENSION__();
10 | const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
11 | const store = createStoreWithMiddleware(reducers, reduxDevToolsEnchancer);
12 |
13 | module.exports = { store };
14 |
--------------------------------------------------------------------------------
/client/external/particlesConfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "particles": {
3 | "number": {
4 | "value": 50,
5 | "density": {
6 | "enable": true,
7 | "value_area": 800
8 | }
9 | },
10 | "color": {
11 | "value": "#ffffff"
12 | },
13 | "shape": {
14 | "type": "circle",
15 | "stroke": {
16 | "width": 0,
17 | "color": "#000000"
18 | },
19 | "polygon": {
20 | "nb_sides": 5
21 | },
22 | "image": {
23 | "src": "img/github.svg",
24 | "width": 100,
25 | "height": 100
26 | }
27 | },
28 | "opacity": {
29 | "value": 0.5,
30 | "random": false,
31 | "anim": {
32 | "enable": false,
33 | "speed": 1,
34 | "opacity_min": 0.1,
35 | "sync": false
36 | }
37 | },
38 | "size": {
39 | "value": 3,
40 | "random": true,
41 | "anim": {
42 | "enable": false,
43 | "speed": 40,
44 | "size_min": 0.1,
45 | "sync": false
46 | }
47 | },
48 | "line_linked": {
49 | "enable": true,
50 | "distance": 150,
51 | "color": "#ffffff",
52 | "opacity": 0.4,
53 | "width": 1
54 | },
55 | "move": {
56 | "enable": true,
57 | "speed": 3,
58 | "direction": "none",
59 | "random": false,
60 | "straight": false,
61 | "out_mode": "out",
62 | "bounce": false,
63 | "attract": {
64 | "enable": false,
65 | "rotateX": 600,
66 | "rotateY": 1200
67 | }
68 | }
69 | },
70 | "interactivity": {
71 | "detect_on": "canvas",
72 | "events": {
73 | "onhover": {
74 | "enable": false,
75 | "mode": "grab"
76 | },
77 | "onclick": {
78 | "enable": true,
79 | "mode": "push"
80 | },
81 | "resize": true
82 | },
83 | "modes": {
84 | "grab": {
85 | "distance": 140,
86 | "line_linked": {
87 | "opacity": 1
88 | }
89 | },
90 | "bubble": {
91 | "distance": 400,
92 | "size": 40,
93 | "duration": 2,
94 | "opacity": 8,
95 | "speed": 3
96 | },
97 | "repulse": {
98 | "distance": 200,
99 | "duration": 0.4
100 | },
101 | "push": {
102 | "particles_nb": 4
103 | },
104 | "remove": {
105 | "particles_nb": 2
106 | }
107 | }
108 | },
109 | "retina_detect": true
110 | }
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Blockchain Community Hackathon
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Routes from './config/Routes.jsx';
4 | import NoMetamask from './components/NoMetamask/NoMetamask';
5 | import initWeb3 from './config/initWeb3';
6 | import { history, store } from './config/storeGenerator';
7 | import ParticlesJson from './external/particlesConfig.json';
8 |
9 | particlesJS("particles-js", ParticlesJson); // eslint-disable-line
10 |
11 | const startApp = () => {
12 | const hasMetamask = initWeb3();
13 |
14 | if (!hasMetamask) {
15 | let isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
16 |
17 | ReactDOM.render( , document.getElementById('root'));
18 | } else {
19 | ReactDOM.render(
20 |
21 |
22 |
, document.getElementById('root'));
23 | }
24 | };
25 |
26 | window.addEventListener('load', startApp);
27 |
--------------------------------------------------------------------------------
/client/modules/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "contractAddress": "0x879716da78A75a44bdfa8F038ce875f99586940a",
3 | "startingBlock": 4202429,
4 | "abi": [{"constant":true,"inputs":[],"name":"currentPeriod","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalContribution","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"returnContributionsToTheSponsors","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_siteUrl","type":"string"},{"name":"_logoUrl","type":"string"}],"name":"contributeToPrizePool","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_teamAddress","type":"address"},{"name":"_memberNames","type":"string"},{"name":"_rewardEligible","type":"bool"}],"name":"registerTeam","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_ethAddress","type":"address"}],"name":"registerJuryMember","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_sortedTeams","type":"address[]"}],"name":"payoutPrizes","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_teamAddress","type":"address"}],"name":"disqualifyTeam","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_address","type":"address"}],"name":"getUserType","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"switchToNextPeriod","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getPrizePoolSize","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"restartPeriod","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_votes","type":"address[]"}],"name":"vote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_juryAddress","type":"address"}],"name":"checkJuryVoted","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newPeriod","type":"uint8"}],"name":"PeriodChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"teamName","type":"string"},{"indexed":false,"name":"teamAddress","type":"address"},{"indexed":false,"name":"memberNames","type":"string"},{"indexed":false,"name":"rewardEligible","type":"bool"}],"name":"TeamRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"juryMemberName","type":"string"},{"indexed":false,"name":"juryMemberAddress","type":"address"}],"name":"JuryMemberAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sponsorName","type":"string"},{"indexed":false,"name":"sponsorSite","type":"string"},{"indexed":false,"name":"sponsorLogoUrl","type":"string"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"SponsorshipReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"juryMemberName","type":"string"},{"indexed":true,"name":"teamAddress","type":"address"},{"indexed":false,"name":"points","type":"uint256"}],"name":"VoteReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"teamName","type":"string"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"PrizePaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"teamAddress","type":"address"}],"name":"TeamDisqualified","type":"event"}]
5 | }
--------------------------------------------------------------------------------
/client/modules/ethereumService.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import contract from './config.json';
3 |
4 | const networkIds = {
5 | mainnet: '1',
6 | morden: '2',
7 | ropsten: '3',
8 | kovan: '42',
9 | };
10 |
11 | let hackathonContract;
12 |
13 | window.onload = () => {
14 | hackathonContract = web3.eth.contract(contract.abi).at(contract.contractAddress);
15 | // console.log(hackathonContract);
16 | };
17 |
18 | export const getWeb3Status = () =>
19 | new Promise((resolve, reject) => {
20 | if (!web3) {
21 | return reject({
22 | message: 'NOT_FOUND',
23 | });
24 | }
25 |
26 | return web3.version.getNetwork((err, netId) => {
27 | if (netId.toString() !== networkIds.kovan) {
28 | return reject({
29 | message: 'WRONG_NETWORK',
30 | });
31 | }
32 |
33 | return resolve();
34 | });
35 | });
36 |
37 | export const getAccount = () => {
38 | if (!web3.eth.accounts || !web3.eth.accounts.length) { return false; }
39 |
40 | return web3.eth.accounts[0];
41 | };
42 |
43 | export const getBlockNumber = () =>
44 | new Promise((resolve, reject) => {
45 | web3.eth.getBlockNumber((error, latestBlock) => {
46 | if (error) {
47 | return reject(error);
48 | }
49 |
50 | return resolve(latestBlock);
51 | });
52 | });
53 |
54 | /* Events */
55 |
56 | export const JuryMemberAddedEvent = (callback) => {
57 | getBlockNumber()
58 | .then(latestBlock => {
59 | hackathonContract.JuryMemberAdded({}, { fromBlock: latestBlock, toBlock: 'latest' })
60 | .watch((error, event) => {
61 | if (error) {
62 | return callback(error, null);
63 | }
64 |
65 | return callback(null, event);
66 | });
67 | })
68 | .catch(error => {
69 | return callback(error, null);
70 | });
71 | };
72 |
73 | export const PeriodChangedEvent = (callback) => {
74 | getBlockNumber()
75 | .then(latestBlock => {
76 | hackathonContract.PeriodChanged({}, { fromBlock: latestBlock, toBlock: 'latest' })
77 | .watch((error, event) => {
78 | if (error) {
79 | return callback(error, null);
80 | }
81 |
82 | console.log(`Period changed event: ${event}`);
83 | return callback(null, event);
84 | });
85 | })
86 | .catch(error => {
87 | return callback(error, null);
88 | });
89 | };
90 |
91 | export const PrizePaidEvent = (callback) => {
92 | getBlockNumber()
93 | .then(latestBlock => {
94 | hackathonContract.PrizePaid({}, { fromBlock: latestBlock, toBlock: 'latest' })
95 | .watch((error, event) => {
96 | if (error) {
97 | return callback(error, null);
98 | }
99 |
100 | console.log(`Prize paid event: ${event}`);
101 | return callback(null, event);
102 | });
103 | })
104 | .catch(error => {
105 | callback(error, null);
106 | });
107 | };
108 |
109 | export const SponsorshipReceivedEvent = (callback) => {
110 | getBlockNumber()
111 | .then(latestBlock => {
112 | hackathonContract.SponsorshipReceived({}, { fromBlock: latestBlock, toBlock: 'latest' })
113 | .watch((error, event) => {
114 | if (error) {
115 | return callback(error, null);
116 | }
117 |
118 | console.log(`Sponsorship received event: ${event}`);
119 | return callback(null, event);
120 | });
121 | })
122 | .catch(error => {
123 | callback(error, null);
124 | });
125 | };
126 |
127 | export const TeamRegisteredEvent = (callback) => {
128 | getBlockNumber()
129 | .then(latestBlock => {
130 | hackathonContract.TeamRegistered({}, { fromBlock: latestBlock, toBlock: 'latest' })
131 | .watch((error, event) => {
132 | if (error) {
133 | return callback(error, null);
134 | }
135 |
136 | return callback(null, event);
137 | });
138 | })
139 | .catch(error => {
140 | callback(error, null);
141 | });
142 | };
143 |
144 | export const VotesReceivedEvent = (callback) => {
145 | getBlockNumber()
146 | .then(latestBlock => {
147 | hackathonContract.VotesReceived({}, { fromBlock: latestBlock, toBlock: 'latest' })
148 | .watch((error, event) => {
149 | if (error) {
150 | return callback(error, null);
151 | }
152 |
153 | return callback(null, event);
154 | });
155 | })
156 | .catch(error => {
157 | callback(error, null);
158 | });
159 | };
160 |
161 | /* Contract functions (prefixed by "_") */
162 |
163 | export const _switchToNextPeriod = () =>
164 | new Promise((resolve, reject) => {
165 | hackathonContract.switchToNextPeriod((error, result) => {
166 | if (error) {
167 | return reject({
168 | message: error,
169 | });
170 | }
171 |
172 | return resolve(result);
173 | });
174 | });
175 |
176 | export const _registerTeam = (name, teamAddress, memberNames, rewardEligible) =>
177 | new Promise((resolve, reject) => {
178 | if (!web3.isAddress(teamAddress)) {
179 | return reject({
180 | message: 'Invalid team address.',
181 | });
182 | }
183 |
184 | return hackathonContract.registerTeam(name, teamAddress, memberNames, !!rewardEligible,
185 | (error, result) => {
186 | if (error) {
187 | return reject({
188 | message: error,
189 | });
190 | }
191 |
192 | return resolve(result);
193 | });
194 | });
195 |
196 | export const _registerJuryMember = (juryMemberName, juryMemberAddress) =>
197 | new Promise((resolve, reject) => {
198 | if (!web3.isAddress(juryMemberAddress)) {
199 | return reject({
200 | message: 'Invalid jury member address.',
201 | });
202 | }
203 |
204 | return hackathonContract.registerJuryMember(juryMemberName, juryMemberAddress, (error, result) => {
205 | if (error) {
206 | return reject({
207 | message: error,
208 | });
209 | }
210 |
211 | return resolve(result);
212 | });
213 | });
214 |
215 | export const _contributeToPrizePool = (name, amount, siteUrl, logoUrl) =>
216 | new Promise((resolve, reject) => {
217 | hackathonContract.contributeToPrizePool(
218 | name.toString(),
219 | siteUrl.toString(),
220 | logoUrl.toString(),
221 | { value: web3.toWei(amount, 'ether') },
222 | (error, result) => {
223 | if (error) {
224 | return reject({
225 | message: error,
226 | });
227 | }
228 |
229 | return resolve(result);
230 | });
231 | });
232 |
233 | export const _vote = (votes) =>
234 | new Promise((resolve, reject) => {
235 | for (let i = 0; i < votes.length; i += 1) {
236 | if (!web3.isAddress(votes[i])) {
237 | return reject({
238 | message: `Team at index ${i} has an invalid address.`,
239 | });
240 | }
241 | }
242 |
243 | return hackathonContract.vote(votes, (error, result) => {
244 | if (error) {
245 | return reject({
246 | message: error,
247 | });
248 | }
249 |
250 | return resolve(result);
251 | });
252 | });
253 |
254 | export const _payoutPrizes = (teamAddresses) =>
255 | new Promise((resolve, reject) => {
256 | console.log(teamAddresses);
257 | hackathonContract.payoutPrizes(
258 | teamAddresses,
259 | (error, result) => {
260 | if (error) {
261 | return reject({
262 | message: error,
263 | });
264 | }
265 |
266 | return resolve(result);
267 | });
268 | });
269 |
270 | /* Getters for contract state */
271 |
272 | export const getTeams = () =>
273 | new Promise((resolve, reject) => {
274 | getDisqualifiedTeams()
275 | .then(disqualifiedTeams => {
276 | hackathonContract.TeamRegistered({}, {
277 | fromBlock: contract.startingBlock, toBlock: 'latest'
278 | })
279 | .get((error, events) => {
280 | if (error) {
281 | return reject({
282 | message: error,
283 | });
284 | }
285 | let alteredEvents = events.map(item => {
286 | if (disqualifiedTeams.indexOf(item.args.teamAddress) === -1) return item;
287 | let alteredEvent = item;
288 | alteredEvent.args.disqualified = true;
289 | return alteredEvent
290 | });
291 | return resolve(alteredEvents);
292 | });
293 | });
294 | });
295 |
296 | export const getDisqualifiedTeams = () =>
297 | new Promise((resolve, reject) => {
298 | hackathonContract.TeamDisqualified({}, {
299 | fromBlock: contract.startingBlock, toBlock: 'latest'
300 | })
301 | .get((error, events) => {
302 | if (error) {
303 | return reject({
304 | message: error,
305 | });
306 | }
307 |
308 | return resolve(events.map(item => item.args.teamAddress));
309 | });
310 | });
311 |
312 | export const getPhase = () =>
313 | new Promise((resolve, reject) => {
314 | hackathonContract.currentPeriod((error, data) => {
315 | if (error) {
316 | return reject({
317 | message: error,
318 | });
319 | }
320 |
321 | return resolve(data.toString(10));
322 | });
323 | });
324 |
325 | export const getJuries = () =>
326 | new Promise((resolve, reject) => {
327 | hackathonContract.JuryMemberAdded({}, {
328 | fromBlock: contract.startingBlock, toBlock: 'latest'
329 | })
330 | .get((error, events) => {
331 | if (error) {
332 | return reject({
333 | message: error,
334 | });
335 | }
336 |
337 | return resolve(events);
338 | });
339 | });
340 |
341 | export const getSponsors = () =>
342 | new Promise((resolve, reject) => {
343 | hackathonContract.SponsorshipReceived({}, {
344 | fromBlock: contract.startingBlock, toBlock: 'latest',
345 | })
346 | .get((error, events) => {
347 | if (error) {
348 | return reject({
349 | message: error,
350 | });
351 | }
352 |
353 | return resolve(events);
354 | });
355 | });
356 |
357 | export const getTeamScores = (teamAddresses) =>
358 | new Promise((resolve, reject) => {
359 | hackathonContract.VoteReceived({}, {
360 | fromBlock: contract.startingBlock, toBlock: 'latest',
361 | })
362 | .get((error, events) => {
363 | if (error) {
364 | return reject({
365 | message: error,
366 | });
367 | }
368 |
369 | return resolve(events);
370 | });
371 | });
372 |
373 | export const getUserType = () =>
374 | new Promise((resolve, reject) => {
375 | hackathonContract.getUserType(getAccount(), (error, result) => {
376 | if (error) {
377 | return reject({
378 | message: error,
379 | });
380 | }
381 |
382 | return resolve(result);
383 | });
384 | });
385 |
386 | export const getUserTypeWithTimeout = () =>
387 | new Promise((resolve, reject) => {
388 | if (getAccount()) {
389 | getUserType()
390 | .then((res) => resolve(res))
391 | .catch((error) => reject(error));
392 | } else {
393 | setTimeout(() => {
394 | getUserType()
395 | .then((res) => resolve(res))
396 | .catch((error) => reject(error));
397 | }, 500);
398 | }
399 | });
400 |
401 | export const checkJuryVoted = () => {
402 | new Promise((resolve) => {
403 | hackathonContract.checkJuryVoted(getAccount(), (error, result) => {
404 | return resolve(result);
405 | });
406 | });
407 |
408 | };
409 |
410 | export const getPrizePoolSize = () =>
411 | new Promise((resolve, reject) => {
412 | hackathonContract.getPrizePoolSize((error, result) => {
413 | if (error) {
414 | return reject({
415 | message: error,
416 | });
417 | }
418 |
419 | return resolve(result);
420 | });
421 | });
422 |
423 | // setTimeout(() => {
424 | // getDisqualifiedTeams()
425 | // .then(data => console.log(data));
426 | // }, 1000);
427 |
--------------------------------------------------------------------------------
/client/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { routerReducer } from 'react-router-redux';
2 | import { reducer as formReducer } from 'redux-form';
3 | import { combineReducers } from 'redux';
4 | import userReducer from './userReducer';
5 | import modalsReducer from './modalsReducer';
6 | import teamsReducer from './teamsReducer';
7 | import sponsorsReducer from './sponsorsReducer';
8 | import judgesReducer from './judgesReducer';
9 |
10 |
11 | export default combineReducers({
12 | form: formReducer,
13 | routing: routerReducer,
14 | user: userReducer,
15 | modals: modalsReducer,
16 | teams: teamsReducer,
17 | sponsors: sponsorsReducer,
18 | judges: judgesReducer,
19 | });
20 |
--------------------------------------------------------------------------------
/client/reducers/judgesReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | JUDGES_FETCH, JUDGES_SUCCESS, JUDGES_ERROR, ADD_JUDGE, ADD_JUDGE_SUCCESS,
3 | ADD_JUDGE_ERROR, NEW_JUDGE
4 | } from '../actions/types';
5 |
6 |
7 | const INITIAL_STATE = {
8 | isFetching: true,
9 | judges: [],
10 | submitting: false,
11 | addJudgeError: false
12 | };
13 |
14 | export default (state = INITIAL_STATE, action) => {
15 | switch (action.type) {
16 | case JUDGES_FETCH:
17 | return {
18 | ...state,
19 | isFetching: true
20 | };
21 |
22 | case JUDGES_SUCCESS:
23 | return {
24 | ...state,
25 | isFetching: false,
26 | judges: action.judges
27 | };
28 |
29 | case JUDGES_ERROR:
30 | return {
31 | ...state,
32 | isFetching: false,
33 | error: action.error
34 | };
35 |
36 | case NEW_JUDGE:
37 | return {
38 | ...state,
39 | judges: [
40 | ...state.judges,
41 | action.event
42 | ]
43 | };
44 |
45 | case ADD_JUDGE:
46 | return {
47 | ...state,
48 | submitting: true
49 | };
50 |
51 | case ADD_JUDGE_SUCCESS:
52 | return {
53 | ...state,
54 | submitting: false,
55 | addJudgeError: false,
56 | };
57 |
58 | case ADD_JUDGE_ERROR:
59 | return {
60 | ...state,
61 | submitting: false,
62 | addJudgeError: action.payload.addSponsorError
63 | };
64 |
65 | default:
66 | return state;
67 | }
68 | };
69 |
--------------------------------------------------------------------------------
/client/reducers/modalsReducer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TOGGLE_MODAL } from '../actions/types';
3 |
4 | const INITIAL_STATE = {
5 | currentOpen: false,
6 | currentModal: () => error
7 | };
8 |
9 | export default (state = INITIAL_STATE, action) => {
10 | switch (action.type) {
11 | case TOGGLE_MODAL:
12 | return {
13 | ...state,
14 | currentModal: action.payload.modalComponent,
15 | currentOpen: action.payload.state
16 | };
17 |
18 | default:
19 | return state;
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/client/reducers/sponsorsReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | SPONSORS_FETCH, SPONSORS_SUCCESS, SPONSORS_ERROR, ADD_SPONSOR, ADD_SPONSOR_SUCCESS,
3 | ADD_SPONSOR_ERROR, NEW_SPONSOR, SPONSORS_PRIZE_ETH, SPONSORS_PRIZE_EUR
4 | } from '../actions/types';
5 |
6 | const INITIAL_STATE = {
7 | isFetching: true,
8 | sponsors: [],
9 | submitting: false,
10 | addSponsorError: false,
11 | ethPrize: '0',
12 | eurPrize: '0',
13 | showThanks: false,
14 | };
15 |
16 | export default (state = INITIAL_STATE, action) => {
17 | switch (action.type) {
18 | case SPONSORS_FETCH:
19 | return {
20 | ...state,
21 | isFetching: true
22 | };
23 |
24 | case SPONSORS_SUCCESS:
25 | return {
26 | ...state,
27 | isFetching: false,
28 | sponsors: action.sponsors
29 | };
30 |
31 | case SPONSORS_ERROR:
32 | return {
33 | ...state,
34 | isFetching: false,
35 | error: action.error
36 | };
37 |
38 | case NEW_SPONSOR:
39 | return {
40 | ...state,
41 | sponsors: [
42 | ...state.sponsors,
43 | action.event,
44 | ]
45 | };
46 |
47 | case ADD_SPONSOR:
48 | return {
49 | ...state,
50 | submitting: true
51 | };
52 |
53 | case ADD_SPONSOR_SUCCESS:
54 | return {
55 | ...state,
56 | submitting: false,
57 | addSponsorError: false,
58 | showThanks: true,
59 | };
60 |
61 | case ADD_SPONSOR_ERROR:
62 | return {
63 | ...state,
64 | submitting: false,
65 | addSponsorError: action.payload.addSponsorError,
66 | showThanks: false,
67 | };
68 |
69 | case SPONSORS_PRIZE_ETH:
70 | return {
71 | ...state,
72 | ethPrize: action.prize,
73 | eurPrize: undefined,
74 | };
75 |
76 | case SPONSORS_PRIZE_EUR:
77 | return {
78 | ...state,
79 | eurPrize: action.prize,
80 | };
81 |
82 | default:
83 | return state;
84 | }
85 | };
86 |
--------------------------------------------------------------------------------
/client/reducers/teamsReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | NEW_TEAM,
3 | TEAM_UP,
4 | TEAM_DOWN,
5 | TEAMS_FETCH,
6 | TEAMS_SUCCESS,
7 | TEAMS_ERROR,
8 | ADD_TEAM,
9 | ADD_TEAM_ERROR,
10 | ADD_TEAM_SUCCESS
11 | } from '../actions/types';
12 |
13 | const INITIAL_STATE = {
14 | isFetching: true,
15 | teams: [],
16 | submitting: false,
17 | addTeamError: false,
18 | error: false
19 | };
20 |
21 | export default (state = INITIAL_STATE, action) => {
22 | switch (action.type) {
23 | case TEAMS_FETCH:
24 | return {
25 | ...state,
26 | isFetching: true,
27 | teams: [],
28 | };
29 |
30 | case TEAMS_SUCCESS:
31 | return {
32 | ...state,
33 | isFetching: false,
34 | teams: action.teams,
35 | error: false,
36 | };
37 |
38 | case TEAMS_ERROR:
39 | return {
40 | ...state,
41 | isFetching: false,
42 | error: action.error
43 | };
44 |
45 | case NEW_TEAM:
46 | return {
47 | ...state,
48 | teams: [
49 | ...state.teams,
50 | action.event,
51 | ]
52 | };
53 |
54 | case ADD_TEAM:
55 | return {
56 | ...state,
57 | submitting: true
58 | };
59 |
60 | case ADD_TEAM_SUCCESS:
61 | return {
62 | ...state,
63 | submitting: false,
64 | addTeamError: false,
65 | };
66 |
67 | case ADD_TEAM_ERROR:
68 | return {
69 | ...state,
70 | submitting: false,
71 | addTeamError: action.payload.addTeamError
72 | };
73 |
74 | case TEAM_UP:
75 | return {
76 | ...state,
77 | teams: [
78 | ...state.teams.slice(0, action.index - 1),
79 | state.teams[action.index],
80 | state.teams[action.index - 1],
81 | ...state.teams.slice(action.index + 1),
82 | ]
83 | };
84 |
85 | case TEAM_DOWN:
86 | return {
87 | ...state,
88 | teams: [
89 | ...state.teams.slice(0, action.index),
90 | state.teams[action.index + 1],
91 | state.teams[action.index],
92 | ...state.teams.slice(action.index + 2),
93 | ]
94 | };
95 |
96 | default:
97 | return state;
98 | }
99 | };
100 |
--------------------------------------------------------------------------------
/client/reducers/userReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | USER_CHECKING, USER_FOUND, PHASE_FETCH, PHASE_FETCH_SUCCESS, PHASE_FETCH_ERROR,
3 | CHANGE_PHASE, CHANGE_PHASE_SUCCESS, CHANGE_PHASE_ERROR,
4 | SUBMIT_PAYOUT, SUBMIT_PAYOUT_SUCCESS, SUBMIT_PAYOUT_ERROR, UPDATE_PHASE,
5 | ALREADY_VOTED, VOTE, VOTE_SUCCESS, VOTE_ERROR
6 | } from '../actions/types';
7 |
8 |
9 | const INITIAL_STATE = {
10 | isDetermined: false,
11 | type: 'other',
12 | phases: ['Registration', 'Competition', 'Voting', 'Verification', 'End'],
13 | phase: 0,
14 | isFetching: false,
15 | phaseError: false,
16 | changingError: false,
17 | changingPhase: false,
18 | submittingPayout: false,
19 | submittingPayoutError: false,
20 | alreadyVoted: false,
21 | voting: false,
22 | votingError: false,
23 | votingSuccess: false
24 | };
25 |
26 | export default (state = INITIAL_STATE, action) => {
27 | switch (action.type) {
28 | case USER_CHECKING:
29 | return state;
30 | case USER_FOUND:
31 | return {
32 | ...state,
33 | isDetermined: true,
34 | type: action.userType,
35 | };
36 | case PHASE_FETCH:
37 | return {
38 | ...state,
39 | isFetching: true
40 | };
41 |
42 | case PHASE_FETCH_SUCCESS:
43 | return {
44 | ...state,
45 | isFetching: false,
46 | phase: action.payload.phase,
47 | phaseError: false
48 | };
49 |
50 | case PHASE_FETCH_ERROR:
51 | return {
52 | ...state,
53 | isFetching: false,
54 | phaseError: 'Error occurred'
55 | };
56 |
57 | case CHANGE_PHASE:
58 | return {
59 | ...state,
60 | changingPhase: true
61 | };
62 |
63 | case CHANGE_PHASE_SUCCESS:
64 | return {
65 | ...state,
66 | changingPhase: false,
67 | changingError: false
68 | };
69 |
70 | case UPDATE_PHASE:
71 | return {
72 | ...state,
73 | phase: action.payload.phase,
74 | };
75 |
76 | case CHANGE_PHASE_ERROR:
77 | return {
78 | ...state,
79 | changingPhase: false,
80 | changingError: 'Error occurred while changing period'
81 | };
82 |
83 | case SUBMIT_PAYOUT:
84 | return {
85 | ...state,
86 | submittingPayout: true
87 | };
88 |
89 | case SUBMIT_PAYOUT_SUCCESS:
90 | return {
91 | ...state,
92 | submittingPayout: false,
93 | submittingPayoutError: false
94 | };
95 |
96 | case SUBMIT_PAYOUT_ERROR:
97 | return {
98 | ...state,
99 | submittingPayout: false,
100 | submittingPayoutError: action.message
101 | };
102 |
103 | case ALREADY_VOTED:
104 | return {
105 | ...state,
106 | alreadyVoted: true
107 | };
108 |
109 | case VOTE:
110 | return {
111 | ...state,
112 | voting: true
113 | };
114 |
115 | case VOTE_SUCCESS:
116 | return {
117 | ...state,
118 | voting: false,
119 | votingError: false,
120 | votingSuccess: true
121 | };
122 |
123 | case VOTE_ERROR:
124 | return {
125 | ...state,
126 | voting: false,
127 | votingSuccess: false,
128 | votingError: 'Error occurred while voting'
129 | };
130 |
131 | default:
132 | return state;
133 | }
134 | };
135 |
--------------------------------------------------------------------------------
/dist/521fb616232adb3eb64828d5a3d820a1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DecenterApps/Smart-Hackathon-DApp/31e6419a47c1a6c9885e35b359466c7fa26eb032/dist/521fb616232adb3eb64828d5a3d820a1.png
--------------------------------------------------------------------------------
/dist/eecc5a0815a9233ebd31eb7d77eda4b1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DecenterApps/Smart-Hackathon-DApp/31e6419a47c1a6c9885e35b359466c7fa26eb032/dist/eecc5a0815a9233ebd31eb7d77eda4b1.png
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Blockchain Community Hackathon
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/dist/main.css:
--------------------------------------------------------------------------------
1 | .footer{text-align:center;bottom:0;width:100%;color:#fff;font-size:16px;margin:50px 0 35px}.footer a:hover{color:#fff;text-decoration:underline}.header{width:100%;max-width:1200px;margin:auto;padding:30px 0;display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between}.header .title{text-align:left}.header .title h1{color:#fff;line-height:30px;font-size:26px;margin:0}.header .title h1 a{transition:all .1}.header .title h1:hover a{color:#fff;opacity:.8}.header .title h2{color:#fff;opacity:.7;font-size:16px;margin:0;font-weight:400}.header .other{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.sk-cube-grid{width:15px;height:15px;margin-right:15px}.sk-cube-grid .sk-cube{width:33%;height:33%;background-color:#fff;float:left;animation:sk-cubeGridScaleDelay 1.3s infinite ease-in-out}.sk-cube-grid .sk-cube1{animation-delay:.2s}.sk-cube-grid .sk-cube2{animation-delay:.3s}.sk-cube-grid .sk-cube3{animation-delay:.4s}.sk-cube-grid .sk-cube4{animation-delay:.1s}.sk-cube-grid .sk-cube5{animation-delay:.2s}.sk-cube-grid .sk-cube6{animation-delay:.3s}.sk-cube-grid .sk-cube7{animation-delay:0s}.sk-cube-grid .sk-cube8{animation-delay:.1s}.sk-cube-grid .sk-cube9{animation-delay:.2s}@keyframes sk-cubeGridScaleDelay{0%,70%,to{transform:scale3D(1,1,1)}35%{transform:scale3D(0,0,1)}}.admin-header-period{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;background-color:transparent;color:#fff;font-size:18px}.admin-header-period .period{margin-right:10px}.user-type-view-link a{font-family:Open Sans,sans-serif;font-weight:700;margin-right:25px;font-size:18px;background:#842e77;padding:12px;color:#fff;border-radius:3px}.user-type-view-link a:hover{text-decoration:underline}.form-name{font-size:24px;text-align:center;margin-bottom:30px}.form-wrapper{width:100%}.form-wrapper .form-item-wrapper{position:relative;margin-bottom:35px}.form-wrapper .form-item-wrapper .form-item{border:2px solid #e2e2e2;border-radius:3px;padding:19px 0 19px 25px;width:100%;font-size:14px}.form-wrapper .form-item-wrapper .form-item-error{border-color:#f57170;outline-color:#f57170}.form-wrapper .form-item-wrapper div.form-item-error{position:absolute;color:#f57170;top:61px}.form-wrapper .form-item-wrapper-checkbox{margin-bottom:20px;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.form-wrapper .form-item-wrapper-checkbox label{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.form-wrapper .form-item-wrapper-checkbox label:hover{color:gray}.form-wrapper .form-item-wrapper-checkbox .form-item-checkbox{cursor:pointer;display:inline-block;position:relative;top:-2px;height:24px;width:24px;margin-right:10px}.form-wrapper .submit-error{font-size:18px;margin-bottom:20px;color:#f57170}.form-wrapper .submit-button{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:100%;color:#fff;background:#673ab7;padding:19px 0;font-size:16px;border-radius:3px}.form-wrapper .submit-button:hover{background:#916dd1}.form-wrapper .submit-button:disabled{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:not-allowed;background:#bda7e3}.loader{display:inline-block;width:40px;height:40px;background-color:#333;border-radius:100%;vertical-align:bottom;margin:0 5px;animation:sk-scaleout 1s infinite ease-in-out}@keyframes sk-scaleout{0%{transform:scale(0)}to{transform:scale(1);opacity:0}}.scoreboard .table{color:#000!important}.scoreboard .container.white{padding:0;overflow:visible}.scoreboard .container-title{padding:30px;font-size:20px;border-bottom:1px solid #dadada}.scoreboard .scoreboard-regular-header{padding:30px;font-size:20px;text-align:left;border-top:1px solid #dadada}.scoreboard .teams .team .medal,.scoreboard .teams .team .reward-amount,.scoreboard .teams .team .team-rank,.scoreboard .teams .team .total-points{font-size:14px;-ms-flex-pack:center;justify-content:center;-ms-flex-positive:0.4;flex-grow:0.4}.scoreboard .teams .team .team-name-wrapper .rewardable{position:relative;top:3px;margin-right:10px}.scoreboard .teams .team .team-name-wrapper .team-name{font-weight:500;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.scoreboard .teams .team .total-points{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;display:-ms-flexbox;display:flex;-ms-flex-positive:0.3;flex-grow:0.3;overflow:visible;cursor:pointer}.scoreboard .teams .team .members{-ms-flex-positive:2;flex-grow:2;display:block;line-height:27px}.scoreboard .teams .team .reward-amount{padding-right:20px;line-height:25px;-ms-flex-positive:0.4;flex-grow:0.4;text-align:right;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end}.scoreboard .teams .team .reward-amount .reward-amount-eth{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.scoreboard .teams .team .reward-amount .eth-icon-wrapper{margin-left:7px;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end}div.tooltips{position:relative;display:inline}div.tooltips .tooltip-wrapper{position:absolute;width:250px;color:#fff;background:#673ab7;top:49px;line-height:25px;text-align:center;visibility:hidden;border-radius:3px}div.tooltips .tooltip-wrapper .jury-row{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;font-size:12px;padding:3px 8px}div.tooltips .tooltip-wrapper .jury-row .jury-name{max-width:185px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}div.tooltips .tooltip-wrapper:after{content:"";position:absolute;top:-8px;transform:rotate(180deg);margin-left:-8px;width:0;height:0;border-top:8px solid #673ab7;border-right:8px solid transparent;border-left:8px solid transparent;right:19px}div:hover.tooltips .tooltip-wrapper{visibility:visible;opacity:.9;bottom:30px;right:10%;z-index:999}*{box-sizing:border-box;font-family:Open Sans,sans-serif}html{font-size:62.5%}body,html{min-height:100%;width:100%}body{font-size:1.6rem;font-family:Arial,sans-serif;margin:0;padding:0;background:#c05228;background:linear-gradient(45deg,#c05228,#9f1b22 50%,#562889)}.container{width:100%;max-width:1200px;margin:auto}.container.white{background-color:#fff;border-radius:3px;padding:0;overflow:hidden}.container.white .content{padding:20px}.container.hero{height:calc(100vh - 200px);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;text-align:center}.container.hero h1{color:#777;font-size:36px;margin:20px;font-weight:400}a{color:inherit;transition:.2s}a,a:focus,a:hover,a:link{text-decoration:none}canvas{position:absolute;top:0;right:0;bottom:0;left:0;z-index:0}#root{position:relative;z-index:1}button{padding:0;border:0;background:none;cursor:pointer}button:focus{outline:0}.app{text-align:center}.app .form-wrapper{display:inline-block;max-width:470px;background:rgba(0,0,0,.1);border-radius:3px;margin:60px 0 40px;padding:30px}.app .form-wrapper form{width:100%;max-width:390px;display:inline-block}.app .form-wrapper .form-item{background:transparent;border-radius:3px;border:2px solid #fff;padding:16px 20px;text-align:left;color:#fff}.app .form-wrapper .form-item::-webkit-input-placeholder{color:#d4d4d4}.app .form-wrapper .form-item::-moz-placeholder{color:#d4d4d4}.app .form-wrapper .form-item:-ms-input-placeholder{color:#d4d4d4}.app .form-wrapper .form-item:-moz-placeholder{color:#d4d4d4}.app .form-wrapper .form-item-error{border-color:#f57170;outline-color:#f57170}.app .form-wrapper .submit-button{background:#fff;color:#a72823;font-weight:700}.app .form-wrapper .submit-button .sk-cube-grid .sk-cube{background-color:#ff9800}.app .form-wrapper .submit-button:hover:not(:disabled){color:#ffb74d}.app .form-wrapper .submit-button:disabled{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:not-allowed;opacity:.5}.app .form-name{color:#fff;font-size:34px;font-weight:700}.table{display:-ms-flexbox;display:flex;-ms-flex-flow:column nowrap;flex-flow:column nowrap;font-size:14px;line-height:1.5;-ms-flex:1 1 auto;flex:1 1 auto;color:#777}.table-info-header{border-top:0!important;font-weight:700}.th{text-align:left}.th>.td{white-space:normal;-ms-flex-pack:center;justify-content:center}.tr{width:100%;display:-ms-flexbox;display:flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap;border-bottom:1px solid #eee}.tr:first-child{border-top:1px solid #eee}.td{display:-ms-flexbox;display:flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-align:center;align-items:center;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;padding:20px 5px;overflow:hidden;text-overflow:ellipsis;min-width:0;white-space:nowrap}.table-header{padding:26px 21px 10px;display:-ms-flexbox;display:flex}.table-header.jury-table-header{-ms-flex-pack:justify;justify-content:space-between}.table-header .button-status-wrapper,.table-header.jury-table-header{display:-ms-flexbox;display:flex}.table-header .button-status-wrapper .voting-status{margin-right:15px;margin-top:5px}.table-header .button-status-wrapper .voting-status.voting-error{color:#f57170}.table-header .button-status-wrapper .voting-status.voting-success{color:#28cc9e}.table-header .title{font-size:20px;color:#212121;margin-bottom:8px;line-height:20px}.table-header .subtitle{color:#757575;font-size:14px;line-height:14px}.table-header .submit-button{margin-left:auto;width:100px;height:38px;background-color:#673ab7;border-radius:2px;font-size:13px;color:#fff}.table-header .submit-button:hover:not(:disabled){background:#916dd1}.table-header .submit-button:disabled{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:not-allowed;background:#bda7e3}.table-header{display:-ms-flexbox;display:flex}.table-header .title{font-size:20px;color:#212121;margin-bottom:8px;line-height:20px}.table-header .subtitle{color:#757575;font-size:14px;line-height:14px}.table-header .submit-button{margin-left:auto;width:100px;height:38px;background-color:#673ab7;border-radius:2px;font-size:13px;color:#fff}.table-header .submit-button:hover:not(:disabled){background:#916dd1}.table-header .submit-button:disabled{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:not-allowed;background:#bda7e3}.modal-header{-ms-flex-pack:end;justify-content:flex-end;border-bottom:0}.modal-header span{display:-ms-flexbox;display:flex;cursor:pointer;margin:5px 10px 0 0}.modal-wrapper-body{padding:6px 60px 60px;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center}.empty-section{min-height:150px;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;text-align:center}.empty-section h1{color:#777;font-size:21px;margin:20px;font-weight:400}.empty-section button{border:0;outline:none;background-color:#673ab7;color:#fff;padding:10px 20px;border-radius:3px;text-transform:uppercase;font-size:14px}.tab-wrapper{display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;-ms-flex-pack:justify;justify-content:space-between;border-bottom:1px solid #eee}.tab-wrapper .left-section a{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:inline-block;color:#bfbfbf;text-align:center;padding:20px;margin:0 10px;border-bottom:3px solid #fff;cursor:pointer}.tab-wrapper .left-section a.active{color:#673ab7;border-color:#673ab7}.tab-wrapper .right-section{cursor:pointer;margin-right:30px}.tab-wrapper .right-section,.tab-wrapper .right-section button{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.tab-wrapper .right-section button{color:#673ab7}.tab-wrapper .right-section button svg{margin-right:7px;fill:#673ab7}.tab-wrapper .right-section button:hover{color:#9f80d7}.tab-wrapper .right-section button:hover svg{fill:#9f80d7}.admin-header{padding:20px}.admin-header h1{color:#777;font-size:21px;margin:20px;font-weight:400}.admin-header button{border:0;outline:none;background-color:#673ab7;color:#fff;padding:10px 20px;border-radius:3px;text-transform:uppercase;font-size:14px}.display-table{width:100%;text-align:left}.display-table tr{border-top:1px solid #eee}.display-table tr:last-child{border-bottom:1px solid #eee}.display-table td,.display-table th{padding:20px 10px}.display-table td:first-child,.display-table th:first-child{padding-left:30px}.display-table td:last-child,.display-table th:last-child{padding-right:30px;text-align:right}.display-table td.highlighted,.display-table th.highlighted{color:#673ab7}.display-table td.rewardable,.display-table th.rewardable{width:40px;line-height:10px;padding-left:20px;padding-right:20px}.display-table th{font-weight:400;font-size:16px}.display-table th.order{font-size:14px;color:#95989a;font-weight:400}.display-table td{font-size:14px;color:#777}.display-table .clickable{cursor:pointer}.display-table .clickable:active{opacity:.8}.display-table .arrows{padding:0 10px;display:inline-block;float:right}.display-table .arrows:first-of-type{padding-right:17px}.top-level-wrapper{overflow:hidden;max-width:1200px;margin:auto;padding:0 30px}.landing-wrapper{text-align:center;padding:40px 0!important;min-height:300px;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center}.landing-wrapper .end-wrapper .table-wrapper{margin-top:30px}.landing-wrapper .end-wrapper .table-wrapper .scoreboard-header{margin-bottom:30px}.landing-wrapper .end-wrapper .table-wrapper .table{margin-bottom:0}.landing-wrapper .end-wrapper .table-wrapper .table .members{text-align:left}.landing-wrapper .end-wrapper .table-wrapper .table .medal{-ms-flex-positive:0.2;flex-grow:0.2;margin-right:28px}.landing-wrapper.last-phase{padding-bottom:0!important;overflow:visible}.landing-wrapper.chrome-metamask-wrapper .browser-wrapper h2{margin-bottom:0}.landing-wrapper.chrome-metamask-wrapper .browser-wrapper a{text-decoration:underline;margin:0 5px}.landing-wrapper.chrome-metamask-wrapper .browser-wrapper.not-chrome h2{line-height:35px}.landing-wrapper p{margin:0}.landing-wrapper p a{text-decoration:underline}.landing-wrapper .sponsors-wrapper{margin:50px 50px 30px}.landing-wrapper .sponsors-wrapper .prize-pool-wrapper{margin-top:20px}.landing-wrapper .sponsors-wrapper .sponsor-wrapper{display:inline-block;margin:0 10px 10px}.landing-wrapper .sponsors-wrapper .sponsor-wrapper .logo{display:block!important;margin:0 auto;height:60px;width:100px;background-size:contain!important;background-position:50%!important;background-repeat:no-repeat!important}.landing-wrapper .table-wrapper{margin:50px 0 0}.landing-wrapper .td.rewardable{max-width:60px;padding-left:20px;padding-right:20px}.landing-wrapper h2{margin-bottom:20px}.payout-wrapper{border-top:1px solid #d0d0d0}.payout-wrapper .payout-error{color:#f57170;font-size:16px;margin-left:20px}.payout-wrapper button{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;color:#fff;background:#673ab7;padding:10px 20px;font-size:16px;border-radius:3px;margin-left:10px}.payout-wrapper button:hover{background:#916dd1}.payout-wrapper button:disabled{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:not-allowed;background:#bda7e3}.payout-wrapper .pay-button-wrapper{font-size:18px;font-weight:700;margin:20px}.payout-wrapper .table-header{display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between;-ms-flex-align:center;align-items:center;border-top:1px solid #d0d0d0;font-size:20px;padding:30px}.payout-wrapper .table-header .tip{font-size:14px}.payout-wrapper .table{border-bottom:0;margin-bottom:0}.payout-wrapper .table .team-name{-ms-flex-positive:0.5;flex-grow:0.5}.payout-wrapper .table .arrows,.payout-wrapper .table .team-points,.payout-wrapper .table .team-rank{-ms-flex-positive:0.2;flex-grow:0.2;-ms-flex-pack:center;justify-content:center}.payout-wrapper .table .team-points{-ms-flex-positive:0.6;flex-grow:0.6}.payout-wrapper .table .arrows{cursor:pointer;-ms-flex-positive:0.2;flex-grow:0.2}.payout-wrapper .table .team-address{-ms-flex-pack:start;justify-content:flex-start}.admin-change-phase{padding:50px 10px;text-align:center;font-size:20px}.admin-change-phase span{margin-right:10px}.admin-change-phase button{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;color:#fff;background:#673ab7;padding:10px 20px;font-size:16px;border-radius:3px}.admin-change-phase button:hover{background:#916dd1}.admin-change-phase button:disabled{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:not-allowed;background:#bda7e3}.admin-change-phase .change-error{margin-top:15px;font-size:16px;color:#f57170}
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "DecenterHackaton",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "repository": "https://github.com/DecenterApps/DecenterHackaton.git",
6 | "license": "MIT",
7 | "scripts": {
8 | "dev": "webpack-dev-server --history-api-fallback --compress --config ./webpack/webpack-dev.config.js",
9 | "prod": "webpack --config ./webpack/webpack-prod.config.js",
10 | "prod-serve": "webpack-dev-server --history-api-fallback --compress --config ./webpack/webpack-prod.config.js"
11 | },
12 | "dependencies": {
13 | "autoprefixer-loader": "^3.2.0",
14 | "babel-plugin-transform-object-rest-spread": "^6.23.0",
15 | "clean-webpack-plugin": "^0.1.16",
16 | "css-loader": "^0.28.4",
17 | "eslint": "^4.4.1",
18 | "eslint-config-airbnb": "^15.1.0",
19 | "eslint-loader": "^1.9.0",
20 | "eslint-plugin-import": "^2.7.0",
21 | "eslint-plugin-jsx-a11y": "^6.0.2",
22 | "eslint-plugin-react": "^7.2.1",
23 | "extract-text-webpack-plugin": "^3.0.0",
24 | "file-loader": "^0.11.2",
25 | "html-webpack-plugin": "^2.30.1",
26 | "image-loader": "^0.0.1",
27 | "image-webpack-loader": "^3.3.1",
28 | "node-sass": "^4.5.3",
29 | "optimize-css-assets-webpack-plugin": "^3.0.0",
30 | "path": "^0.12.7",
31 | "prop-types": "^15.5.10",
32 | "react": "^15.6.1",
33 | "react-dom": "^15.6.1",
34 | "react-modal-bootstrap": "^1.1.1",
35 | "react-redux": "^5.0.6",
36 | "react-router": "^4.1.2",
37 | "react-router-dom": "^4.1.2",
38 | "react-router-redux": "^4.0.8",
39 | "redux": "^3.7.2",
40 | "redux-form": "^7.0.3",
41 | "redux-thunk": "^2.2.0",
42 | "sass-loader": "^6.0.6",
43 | "scriptjs": "^2.5.8",
44 | "style-loader": "^0.18.2",
45 | "web3": "^0.20.1",
46 | "webpack": "^3.5.4",
47 | "webpack-dev-server": "^2.7.1"
48 | },
49 | "devDependencies": {
50 | "babel-core": "^6.25.0",
51 | "babel-loader": "^7.1.1",
52 | "babel-plugin-transform-exponentiation-operator": "^6.24.1",
53 | "babel-preset-es2015": "^6.24.1",
54 | "babel-preset-react": "^6.24.1"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/solidity/DecenterHackathon.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.15;
2 |
3 | contract DecenterHackathon {
4 |
5 | struct Team {
6 | string name;
7 | string memberNames;
8 | uint score;
9 | uint reward;
10 | bool rewardEligible;
11 | bool submittedByAdmin;
12 | bool disqualified;
13 | mapping(address => bool) votedForByJuryMember;
14 | }
15 |
16 | struct JuryMember {
17 | string name;
18 | bool hasVoted;
19 | }
20 |
21 | struct Sponsor {
22 | string name;
23 | string siteUrl;
24 | string logoUrl;
25 | address ethAddress;
26 | uint contribution;
27 | }
28 |
29 | enum Period { Registration, Competition, Voting, Verification, End }
30 |
31 | uint public totalContribution;
32 | Period public currentPeriod;
33 |
34 | mapping(address => Team) teams;
35 | mapping(address => JuryMember) juryMembers;
36 |
37 | address administrator;
38 | address[] teamAddresses;
39 | address[] juryMemberAddresses;
40 | Sponsor[] sponsors;
41 |
42 | event PeriodChanged(Period newPeriod);
43 | event TeamRegistered(string teamName, address teamAddress, string memberNames, bool rewardEligible);
44 | event JuryMemberAdded(string juryMemberName, address juryMemberAddress);
45 | event SponsorshipReceived(string sponsorName, string sponsorSite, string sponsorLogoUrl, uint amount);
46 | event VoteReceived(string juryMemberName, address indexed teamAddress, uint points);
47 | event PrizePaid(string teamName, uint amount);
48 | event TeamDisqualified(address teamAddress);
49 |
50 | modifier onlyOwner {
51 | require(msg.sender == administrator);
52 | _;
53 | }
54 |
55 | modifier onlyJury {
56 | require(bytes(juryMembers[msg.sender].name).length > 0);
57 | _;
58 | }
59 |
60 | function DecenterHackathon() {
61 | administrator = msg.sender;
62 | currentPeriod = Period.Registration;
63 | }
64 |
65 | // Administrator is able to switch between periods at any time
66 | function switchToNextPeriod() onlyOwner {
67 | if(currentPeriod == Period.Verification || currentPeriod == Period.End) {
68 | return;
69 | }
70 |
71 | currentPeriod = Period(uint(currentPeriod) + 1);
72 |
73 | PeriodChanged(currentPeriod);
74 | }
75 |
76 | // Administrator can add new teams during registration period, with an option to make a team non-eligible for the prize
77 | function registerTeam(string _name, address _teamAddress, string _memberNames, bool _rewardEligible) onlyOwner {
78 | require(currentPeriod == Period.Registration);
79 | require(bytes(teams[_teamAddress].name).length == 0);
80 |
81 | teams[_teamAddress] = Team({
82 | name: _name,
83 | memberNames: _memberNames,
84 | score: 0,
85 | reward: 0,
86 | rewardEligible: _rewardEligible,
87 | submittedByAdmin: false,
88 | disqualified: false
89 | });
90 |
91 | teamAddresses.push(_teamAddress);
92 | TeamRegistered(_name, _teamAddress, _memberNames, _rewardEligible);
93 | }
94 |
95 | // Administrator can add new jury members during registration period
96 | function registerJuryMember(string _name, address _ethAddress) onlyOwner {
97 | require(currentPeriod == Period.Registration);
98 |
99 | juryMemberAddresses.push(_ethAddress);
100 | juryMembers[_ethAddress] = JuryMember({
101 | name: _name,
102 | hasVoted: false
103 | });
104 |
105 | JuryMemberAdded(_name, _ethAddress);
106 | }
107 |
108 | // Anyone can contribute to the prize pool (i.e. either sponsor himself or administrator on behalf of the sponsor) during registration period
109 | function contributeToPrizePool(string _name, string _siteUrl, string _logoUrl) payable {
110 | require(currentPeriod != Period.End);
111 | require(msg.value >= 0.1 ether);
112 |
113 | sponsors.push(Sponsor({
114 | name: _name,
115 | siteUrl: _siteUrl,
116 | logoUrl: _logoUrl,
117 | ethAddress: msg.sender,
118 | contribution: msg.value
119 | }));
120 |
121 | totalContribution += msg.value;
122 | SponsorshipReceived(_name, _siteUrl, _logoUrl, msg.value);
123 | }
124 |
125 | // Jury members can vote during voting period
126 | // The _votes parameter should be an array of team addresses, sorted by score from highest to lowest based on jury member's preferences
127 | function vote(address[] _votes) onlyJury {
128 | require(currentPeriod == Period.Voting);
129 | require(_votes.length == teamAddresses.length);
130 | require(juryMembers[msg.sender].hasVoted == false);
131 |
132 | uint _points = _votes.length;
133 |
134 | for(uint i = 0; i < _votes.length; i++) {
135 | address teamAddress = _votes[i];
136 |
137 | // All submitted teams must be registered
138 | require(bytes(teams[teamAddress].name).length > 0);
139 |
140 | // Judge should not be able to vote for the same team more than once
141 | require(teams[teamAddress].votedForByJuryMember[msg.sender] == false);
142 |
143 | teams[teamAddress].score += _points;
144 | teams[teamAddress].votedForByJuryMember[msg.sender] = true;
145 |
146 | VoteReceived(juryMembers[msg.sender].name, teamAddress, _points);
147 | _points--;
148 | }
149 |
150 | // This will prevent jury members from voting more than once
151 | juryMembers[msg.sender].hasVoted = true;
152 | }
153 |
154 | // Administrator can initiate prize payout during final period
155 | // The _sortedTeams parameter should be an array of correctly sorted teams by score, from highest to lowest
156 | function payoutPrizes(address[] _sortedTeams) onlyOwner {
157 | require(currentPeriod == Period.Verification);
158 | require(_sortedTeams.length == teamAddresses.length);
159 |
160 | for(uint i = 0; i < _sortedTeams.length; i++) {
161 | // All submitted teams must be registered
162 | require(bytes(teams[_sortedTeams[i]].name).length > 0);
163 |
164 | // Teams must be sorted correctly
165 | require(i == _sortedTeams.length - 1 || teams[_sortedTeams[i + 1]].score <= teams[_sortedTeams[i]].score);
166 |
167 | teams[_sortedTeams[i]].submittedByAdmin = true;
168 | }
169 |
170 | // Prizes are paid based on logarithmic scale, where first teams receives 1/2 of the prize pool, second 1/4 and so on
171 | uint prizePoolDivider = 2;
172 |
173 | for(i = 0; i < _sortedTeams.length; i++) {
174 | // Make sure all teams are included in _sortedTeams array
175 | // (i.e. the array should contain unique elements)
176 | require(teams[_sortedTeams[i]].submittedByAdmin);
177 |
178 | uint _prizeAmount = totalContribution / prizePoolDivider;
179 |
180 | if(teams[_sortedTeams[i]].rewardEligible && !teams[_sortedTeams[i]].disqualified) {
181 | _sortedTeams[i].transfer(_prizeAmount);
182 | teams[_sortedTeams[i]].reward = _prizeAmount;
183 | prizePoolDivider *= 2;
184 | PrizePaid(teams[_sortedTeams[i]].name, _prizeAmount);
185 | }
186 | }
187 |
188 | // Some small amount of ETH might remain in the contract after payout, becuase rewards are determened logarithmically
189 | // This amount is returned to contract owner to cover deployment and transaction costs
190 | // In case this amount turns out to be significantly larger than these costs, the administrator will distribute it to all teams equally
191 | administrator.transfer(this.balance);
192 |
193 | currentPeriod = Period.End;
194 | PeriodChanged(currentPeriod);
195 | }
196 |
197 | // Administrator can disqualify team
198 | function disqualifyTeam(address _teamAddress) onlyOwner {
199 | require(bytes(teams[_teamAddress].name).length > 0);
200 |
201 | teams[_teamAddress].disqualified = true;
202 | TeamDisqualified(_teamAddress);
203 | }
204 |
205 | // In case something goes wrong and contract needs to be redeployed, this is a way to return all contributions to the sponsors
206 | function returnContributionsToTheSponsors() onlyOwner {
207 | for(uint i = 0; i < sponsors.length; i++) {
208 | sponsors[i].ethAddress.transfer(sponsors[i].contribution);
209 | }
210 | }
211 |
212 | // Public function that returns user type for the given address
213 | function getUserType(address _address) constant returns (string) {
214 | if(_address == administrator) {
215 | return "administrator";
216 | } else if(bytes(juryMembers[_address].name).length > 0) {
217 | return "jury";
218 | } else {
219 | return "other";
220 | }
221 | }
222 |
223 | // Check if jury member voted
224 | function checkJuryVoted(address _juryAddress) constant returns (bool){
225 | require(bytes(juryMembers[_juryAddress].name).length != 0);
226 |
227 | return juryMembers[_juryAddress].hasVoted;
228 | }
229 |
230 | // Returns total prize pool size
231 | function getPrizePoolSize() constant returns (uint) {
232 | return totalContribution;
233 | }
234 |
235 | function restartPeriod() onlyOwner {
236 | currentPeriod = Period.Registration;
237 | }
238 | }
--------------------------------------------------------------------------------
/webpack/webpack-dev.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const webpack = require('webpack');
4 |
5 | const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
6 | template: './client/index.html',
7 | filename: 'index.html',
8 | inject: 'body'
9 | });
10 |
11 | module.exports = {
12 | entry: './client/index.js',
13 | devtool: 'inline-source-map',
14 | output: {
15 | path: path.resolve('dist'),
16 | filename: 'index_bundle.js'
17 | },
18 | module: {
19 | loaders: [
20 | { test: /\.js$/, loaders: ['babel-loader', 'eslint-loader'], exclude: /node_modules/ },
21 | { test: /\.jsx$/, loaders: ['babel-loader', 'eslint-loader'], exclude: /node_modules/ },
22 | {
23 | test: /\.(jpe?g|png|gif|svg)$/i,
24 | loaders: [
25 | 'file-loader?hash=sha512&digest=hex&name=[hash].[ext]',
26 | 'image-webpack-loader?bypassOnDebug&optimizationLevel=7&interlaced=false'
27 | ]
28 | },
29 | {
30 | test: /\.scss$/,
31 | loader: 'style-loader!css-loader?modules&importLoaders=2&sourceMap&localIdentName=[local]!autoprefixer-loader?browsers=last 2 version!sass-loader?outputStyle=expanded&sourceMap'
32 | }
33 | ]
34 | },
35 | resolve: {
36 | extensions: ['.js', '.jsx']
37 | },
38 | plugins: [
39 | HtmlWebpackPluginConfig,
40 | new webpack.DefinePlugin({
41 | 'process.env': {
42 | env: '"development"'
43 | }
44 | })
45 | ]
46 | };
47 |
--------------------------------------------------------------------------------
/webpack/webpack-prod.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const webpack = require('webpack');
4 | const CleanPlugin = require('clean-webpack-plugin');
5 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
6 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
7 |
8 | const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
9 | template: './client/index.html',
10 | filename: 'index.html',
11 | inject: 'body'
12 | });
13 |
14 | module.exports = {
15 | entry: './client/index.js',
16 | output: {
17 | path: path.resolve('dist'),
18 | filename: '[name].js',
19 | chunkFilename: '[chunkhash]-[chunkhash].js',
20 | },
21 | module: {
22 | loaders: [
23 | { test: /\.js$/, loaders: ['babel-loader', 'eslint-loader'], exclude: /node_modules/ },
24 | { test: /\.jsx$/, loaders: ['babel-loader', 'eslint-loader'], exclude: /node_modules/ },
25 | {
26 | test: /\.(jpe?g|png|gif|svg)$/i,
27 | loaders: [
28 | 'file-loader?hash=sha512&digest=hex&name=[hash].[ext]',
29 | 'image-webpack-loader?bypassOnDebug&optimizationLevel=7&interlaced=false'
30 | ]
31 | },
32 | {
33 | test: /\.scss$/,
34 | use: ExtractTextPlugin.extract({
35 | fallback: 'style-loader',
36 | use: ['css-loader?modules&importLoaders=2&localIdentName=[local]&sourceMap&minimize=true', 'sass-loader?outputStyle=expanded&sourceMap=true&sourceMapContents=true', 'autoprefixer-loader?browsers=last 2 version']
37 | })
38 | }
39 | ]
40 | },
41 | resolve: {
42 | extensions: ['.js', '.jsx']
43 | },
44 | plugins: [
45 | new CleanPlugin([path.resolve('dist')], { root: path.resolve(__dirname, '../') }),
46 | new ExtractTextPlugin('[name].css', {allChunks: true}),
47 | HtmlWebpackPluginConfig,
48 | // optimizations
49 | new OptimizeCssAssetsPlugin({
50 | assetNameRegExp: /\.optimize\.css$/g,
51 | cssProcessor: require('cssnano'),
52 | cssProcessorOptions: { discardComments: {removeAll: true } },
53 | canPrint: true
54 | }),
55 | new webpack.optimize.OccurrenceOrderPlugin(),
56 | new webpack.optimize.UglifyJsPlugin({
57 | compress: {
58 | warnings: false,
59 | },
60 | }),
61 | new webpack.DefinePlugin({
62 | 'process.env': {
63 | env: '"production"',
64 | NODE_ENV: '"production"',
65 | }
66 | })
67 | ]
68 | };
69 |
--------------------------------------------------------------------------------