├── .gitignore ├── src ├── config.js ├── utils │ ├── initialiseUserId.js │ ├── electionDecrypt.js │ └── encryption.js └── services │ └── qv.js ├── static ├── csv │ ├── voters.csv │ └── candidates.csv └── css │ └── bootstrap.min.css ├── README.md ├── .eslintrc.js ├── pages ├── _document.js ├── index.js ├── share.js ├── share-private.js ├── insights.js ├── election.js ├── vote.js └── create.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | out -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | export const API_BASE_URL = "https://api.qv.geek.sg"; 2 | -------------------------------------------------------------------------------- /static/csv/voters.csv: -------------------------------------------------------------------------------- 1 | name;email 2 | voter 1;voter1@example.com 3 | voter 2;voter2@example.com -------------------------------------------------------------------------------- /static/csv/candidates.csv: -------------------------------------------------------------------------------- 1 | title;description 2 | Candidate 1;This is a good candidate 3 | Candidate 2;This is a good candidate too -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quadratic Voting Frontend Application 2 | 3 | ## Demo 4 | 5 | This application is hosted on https://qv.geek.sg/ 6 | 7 | ## Development 8 | 9 | [Next.js](https://nextjs.org/) is used to generate a static site for the QV web application. 10 | 11 | ```sh 12 | npm install 13 | npm run dev 14 | ``` 15 | 16 | ## Deployment 17 | 18 | Once the code is ready to be deployed, the static site will be built in the `out` folder. 19 | 20 | ```sh 21 | npm run build 22 | ``` 23 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2020: true, 5 | }, 6 | extends: [ 7 | "plugin:react/recommended", 8 | "plugin:prettier/recommended", 9 | "prettier", 10 | "prettier/react", 11 | ], 12 | parserOptions: { 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | ecmaVersion: 11, 17 | sourceType: "module", 18 | }, 19 | plugins: ["react"], 20 | rules: { 21 | "react/react-in-jsx-scope": "off", 22 | "react/prop-types": [0], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/utils/initialiseUserId.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { v4 as uuid } from "uuid"; 3 | 4 | const initialiseUserId = (setUserId) => { 5 | useEffect(() => { 6 | const userIdFromLocalstorage = localStorage.getItem("userId"); 7 | if (!userIdFromLocalstorage) { 8 | const newUserId = uuid(); 9 | localStorage.setItem("userId", newUserId); 10 | setUserId(newUserId); 11 | } else { 12 | setUserId(userIdFromLocalstorage); 13 | } 14 | }); 15 | }; 16 | 17 | export default initialiseUserId; 18 | -------------------------------------------------------------------------------- /src/utils/electionDecrypt.js: -------------------------------------------------------------------------------- 1 | import { decryptStringWithPrivateKey } from "./encryption"; 2 | 3 | export const decryptElectionResults = async (election, privateKey) => { 4 | const decryptedVotesDefered = election.votes.map(async (vote) => { 5 | const decryptedVote = await decryptStringWithPrivateKey( 6 | vote.encryptedVote, 7 | privateKey 8 | ); 9 | return { ...vote, votes: JSON.parse(decryptedVote) }; 10 | }); 11 | const decryptedVotes = await Promise.all(decryptedVotesDefered); 12 | return { ...election, votes: decryptedVotes }; 13 | }; 14 | -------------------------------------------------------------------------------- /pages/_document.js: -------------------------------------------------------------------------------- 1 | import Document, { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | class MyDocument extends Document { 4 | static async getInitialProps(ctx) { 5 | const initialProps = await Document.getInitialProps(ctx); 6 | return { ...initialProps }; 7 | } 8 | 9 | render() { 10 | return ( 11 | 12 | 13 | 14 |