Browse Source

feat(pettier) : eslintrc.json and format all files

pull/73/head
Clement G 3 years ago
parent
commit
e6482a7962
  1. 26
      .eslintrc.json
  2. 24
      src/App.jsx
  3. 17
      src/AppContext.jsx
  4. 15
      src/Routes.jsx
  5. 40
      src/Util.jsx
  6. 16
      src/components/CopyField.jsx
  7. 32
      src/components/banner/Helloasso.jsx
  8. 60
      src/components/banner/Paypal.jsx
  9. 24
      src/components/layouts/Footer.jsx
  10. 15
      src/components/layouts/Header.jsx
  11. 47
      src/components/layouts/LanguageSelector.jsx
  12. 16
      src/components/loader/index.jsx
  13. 20
      src/components/views/CreateElection.jsx
  14. 72
      src/components/views/CreateSuccess.jsx
  15. 12
      src/components/views/Home.jsx
  16. 202
      src/components/views/Result.jsx
  17. 10
      src/components/views/UnknownElection.jsx
  18. 168
      src/components/views/Vote.jsx
  19. 12
      src/components/views/VoteSuccess.jsx
  20. 7
      src/components/wait/index.jsx
  21. 28
      src/i18n.jsx
  22. 21
      src/scss/_app.scss

26
.eslintrc.json

@ -0,0 +1,26 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": [
"react"
],
"rules": {
}
}

24
src/App.jsx

@ -1,19 +1,19 @@
import React from 'react';
import React from "react";
import Routes from './Routes';
import Header from './components/layouts/Header';
import Footer from './components/layouts/Footer';
import AppContextProvider from './AppContext';
import Routes from "./Routes";
import Header from "./components/layouts/Header";
import Footer from "./components/layouts/Footer";
import AppContextProvider from "./AppContext";
function App() {
return (
<AppContextProvider>
<div>
<Header />
<Routes />
<Footer />
</div>
</AppContextProvider>
<AppContextProvider>
<div>
<Header />
<Routes />
<Footer />
</div>
</AppContextProvider>
);
}

17
src/AppContext.jsx

@ -1,7 +1,6 @@
import React, { createContext, Suspense } from "react";
import {BrowserRouter as Router} from 'react-router-dom';
import Loader from './components/loader';
import { BrowserRouter as Router } from "react-router-dom";
import Loader from "./components/loader";
export const AppContext = createContext();
@ -16,12 +15,12 @@ const AppContextProvider = ({ children }) => {
}
};
return (
<Suspense fallback={<Loader/>} >
<Router>
<AppContext.Provider value={defaultState}>
{children}
</AppContext.Provider>
</Router>
<Suspense fallback={<Loader />}>
<Router>
<AppContext.Provider value={defaultState}>
{children}
</AppContext.Provider>
</Router>
</Suspense>
);
};

15
src/Routes.jsx

@ -10,7 +10,6 @@ import UnknownElection from "./components/views/UnknownElection";
import CreateSuccess from "./components/views/CreateSuccess";
import VoteSuccess from "./components/views/VoteSuccess";
function Routes() {
return (
<main className="d-flex flex-column justify-content-center">
@ -19,8 +18,18 @@ function Routes() {
<Route path="/create-election" component={CreateElection} />
<Route path="/vote/:slug" component={Vote} />
<Route path="/result/:slug" component={Result} />
<Route path="/link/:slug" component={props => <CreateSuccess invitationOnly={true} {...props} />} />
<Route path="/links/:slug" component={props => <CreateSuccess invitationOnly={false} {...props} />} />
<Route
path="/link/:slug"
component={props => (
<CreateSuccess invitationOnly={true} {...props} />
)}
/>
<Route
path="/links/:slug"
component={props => (
<CreateSuccess invitationOnly={false} {...props} />
)}
/>
<Route path="/vote-success/:slug" component={VoteSuccess} />
<Route path="/unknown-election/:slug" component={UnknownElection} />
<Route component={UnknownView} />

40
src/Util.jsx

@ -1,34 +1,26 @@
import i18n from './i18n.jsx';
import i18n from "./i18n.jsx";
const colors = [
'#015411',
'#019812',
'#6bca24',
'#ffb200',
'#ff5d00',
'#b20616',
'#6f0214',
"#015411",
"#019812",
"#6bca24",
"#ffb200",
"#ff5d00",
"#b20616",
"#6f0214"
];
const gradeNames = [
'Excellent',
'Very good',
'Good',
'Fair',
'Passable',
'Insufficient',
'To reject',
"Excellent",
"Very good",
"Good",
"Fair",
"Passable",
"Insufficient",
"To reject"
];
const gradeValues = [
6,
5,
4,
3,
2,
1,
0
];
const gradeValues = [6, 5, 4, 3, 2, 1, 0];
export const grades = gradeNames.map((name, i) => ({
label: name,

16
src/components/CopyField.jsx

@ -1,7 +1,6 @@
import React from 'react';
import {Button} from 'reactstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import React from "react";
import { Button } from "reactstrap";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const CopyField = props => {
const ref = React.createRef();
@ -13,10 +12,10 @@ const CopyField = props => {
const input = ref.current;
input.focus();
input.select();
document.execCommand('copy');
document.execCommand("copy");
};
const {t, value, icon} = props;
const { t, value, icon } = props;
return (
<div className="input-group ">
@ -33,9 +32,10 @@ const CopyField = props => {
<Button
className="btn btn-outline-light"
onClick={handleClickOnButton}
type="button">
type="button"
>
<FontAwesomeIcon icon={icon} className="mr-2" />
{t('Copy')}
{t("Copy")}
</Button>
</div>
</div>

32
src/components/banner/Helloasso.jsx

@ -1,17 +1,23 @@
import React from 'react';
import i18n from '../../i18n'
import React from "react";
import i18n from "../../i18n";
const Helloasso = props => {
const locale =
i18n.language.substring(0, 2).toLowerCase() === "fr" ? "fr" : "en";
const linkHelloAssoBanner =
locale === "fr"
? "https://www.helloasso.com/associations/mieux-voter/formulaires/1/widget"
: "https://www.helloasso.com/associations/mieux-voter/formulaires/1/widget/en";
const Helloasso = (props) => {
const locale=i18n.language.substring(0,2).toLowerCase()==="fr"?"fr":"en";
const linkHelloAssoBanner=(locale==="fr")?"https://www.helloasso.com/associations/mieux-voter/formulaires/1/widget":"https://www.helloasso.com/associations/mieux-voter/formulaires/1/widget/en";
return (
<a href={linkHelloAssoBanner} target="_blank" rel="noopener noreferrer">
<img src={"/banner/"+locale+"/helloasso.png"} alt="support us on helloasso" style={{width:props.width}} />
</a>);
return (
<a href={linkHelloAssoBanner} target="_blank" rel="noopener noreferrer">
<img
src={"/banner/" + locale + "/helloasso.png"}
alt="support us on helloasso"
style={{ width: props.width }}
/>
</a>
);
};
export default Helloasso;
export default Helloasso;

60
src/components/banner/Paypal.jsx

@ -1,29 +1,43 @@
import React from 'react';
import i18n from '../../i18n'
import {withTranslation} from 'react-i18next';
import React from "react";
import i18n from "../../i18n";
import { withTranslation } from "react-i18next";
import { faPaypal } from "@fortawesome/free-brands-svg-icons";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const Paypal = props => {
const { t } = props;
let localeStringShort = i18n.language.substring(0, 2);
let localeStringComplete =
localeStringShort.toLowerCase() + "_" + localeStringShort.toUpperCase();
if (localeStringComplete === "en_EN") {
localeStringComplete = "en_US";
}
const pixelLink =
"https://www.paypal.com/" + localeStringComplete + "/i/scr/pixel.gif";
const Paypal = (props) => {
const {t} = props;
let localeStringShort=i18n.language.substring(0,2);
let localeStringComplete=localeStringShort.toLowerCase()+"_"+localeStringShort.toUpperCase();
if(localeStringComplete==="en_EN"){
localeStringComplete="en_US";
}
const pixelLink="https://www.paypal.com/"+localeStringComplete+"/i/scr/pixel.gif";
return (
<div className="d-inline-block m-auto">
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<button type="submit" className={"btn "+props.btnColor} title={t("PayPal - The safer, easier way to pay online!")} > <FontAwesomeIcon icon={faPaypal} className="mr-2" />{t("Support us !")}</button>
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="hidden" name="hosted_button_id" value="KB2Z7L9KARS7C" />
<img alt="" border="0" src={pixelLink} width="1" height="1" />
</form></div>);
return (
<div className="d-inline-block m-auto">
<form
action="https://www.paypal.com/cgi-bin/webscr"
method="post"
target="_top"
>
<button
type="submit"
className={"btn " + props.btnColor}
title={t("PayPal - The safer, easier way to pay online!")}
>
{" "}
<FontAwesomeIcon icon={faPaypal} className="mr-2" />
{t("Support us !")}
</button>
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="hidden" name="hosted_button_id" value="KB2Z7L9KARS7C" />
<img alt="" border="0" src={pixelLink} width="1" height="1" />
</form>
</div>
);
};
export default withTranslation()(Paypal);
export default withTranslation()(Paypal);

24
src/components/layouts/Footer.jsx

@ -1,7 +1,7 @@
import React, {Component} from 'react';
import {withTranslation} from 'react-i18next';
import {Link} from 'react-router-dom';
import Paypal from '../banner/Paypal';
import React, { Component } from "react";
import { withTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import Paypal from "../banner/Paypal";
class Footer extends Component {
constructor(props) {
@ -10,28 +10,30 @@ class Footer extends Component {
}
render() {
const linkStyle = {whiteSpace: 'nowrap'};
const {t} = this.props;
const linkStyle = { whiteSpace: "nowrap" };
const { t } = this.props;
return (
<footer className="text-center">
<Link to="/" style={linkStyle}>
{t('Homepage')}
{t("Homepage")}
</Link>
<span className="m-2">-</span>
<a
href="https://github.com/MieuxVoter"
target="_blank"
rel="noopener noreferrer"
style={linkStyle}>
{t('Source code')}
style={linkStyle}
>
{t("Source code")}
</a>
<span className="m-2">-</span>
<a
href="https://mieuxvoter.fr/"
target="_blank"
rel="noopener noreferrer"
style={linkStyle}>
{t('Who are we?')}
style={linkStyle}
>
{t("Who are we?")}
</a>
<div className="mt-3">
<Paypal btnColor="btn-primary" />

15
src/components/layouts/Header.jsx

@ -1,8 +1,7 @@
import React, { Component } from "react";
import { Collapse, Navbar, NavbarToggler, Nav, NavItem } from "reactstrap";
import { Link } from "react-router-dom";
import { withTranslation } from 'react-i18next';
import { withTranslation } from "react-i18next";
import logo from "../../logos/logo-color.svg";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -10,15 +9,15 @@ import { faRocket } from "@fortawesome/free-solid-svg-icons";
import LanguageSelector from "./LanguageSelector";
class Header extends Component {
state = {
isOpen: false
};
state = {
isOpen: false
};
toggle = () => {
this.setState({
isOpen: !this.state.isOpen
});
}
};
render() {
const { t } = this.props;
@ -33,7 +32,7 @@ class Header extends Component {
<div className="align-self-center ml-2">
<div className="logo-text">
<h1>
{t("Voting platform")}
{t("Voting platform")}
<small>{t("Majority Judgment")}</small>
</h1>
</div>
@ -49,7 +48,7 @@ class Header extends Component {
{t("Start an election")}
</Link>
</NavItem>
<NavItem style={{width:"150px"}}>
<NavItem style={{ width: "150px" }}>
<LanguageSelector />
</NavItem>
</Nav>

47
src/components/layouts/LanguageSelector.jsx

@ -1,30 +1,29 @@
import React from 'react';
import ReactFlagsSelect from 'react-flags-select';
import 'react-flags-select/css/react-flags-select.css';
import i18n from '../../i18n'
import React from "react";
import ReactFlagsSelect from "react-flags-select";
import "react-flags-select/css/react-flags-select.css";
import i18n from "../../i18n";
const LanguageSelector = () => {
const selectHandler = (e) => {
let locale=e.toLowerCase();
if(locale==="gb")locale="en";
i18n.changeLanguage(locale);
};
let locale=i18n.language.substring(0,2).toUpperCase();
if(locale==="EN")locale="GB";
return (<ReactFlagsSelect onSelect={selectHandler}
countries={["GB", "FR", "ES", "DE", "RU"]}
showOptionLabel={false}
defaultCountry={locale}
selectedSize={15}
optionsSize={22}
showSelectedLabel={false}
/>);
const selectHandler = e => {
let locale = e.toLowerCase();
if (locale === "gb") locale = "en";
i18n.changeLanguage(locale);
};
let locale = i18n.language.substring(0, 2).toUpperCase();
if (locale === "EN") locale = "GB";
return (
<ReactFlagsSelect
onSelect={selectHandler}
countries={["GB", "FR", "ES", "DE", "RU"]}
showOptionLabel={false}
defaultCountry={locale}
selectedSize={15}
optionsSize={22}
showSelectedLabel={false}
/>
);
};
export default LanguageSelector;

16
src/components/loader/index.jsx

@ -1,13 +1,13 @@
import React from 'react';
import logo from './loader-pulse-2.gif';
import './style.css';
import React from "react";
import logo from "./loader-pulse-2.gif";
import "./style.css";
const Loader = () => {
return (
<div className="loader bg-primary">
<img src={logo} alt="Loading..." />
</div>);
return (
<div className="loader bg-primary">
<img src={logo} alt="Loading..." />
</div>
);
};
export default Loader;

20
src/components/views/CreateElection.jsx

@ -46,23 +46,31 @@ const AT_LEAST_2_CANDIDATES_ERROR = "Please add at least 2 candidates.";
const NO_TITLE_ERROR = "Please add a title.";
const isValidDate = date => date instanceof Date && !isNaN(date);
const getOnlyValidDate = date => isValidDate(date)?date:new Date();
const getOnlyValidDate = date => (isValidDate(date) ? date : new Date());
// Convert a Date object into YYYY-MM-DD
const dateToISO = date => getOnlyValidDate(date).toISOString().substring(0, 10);
const dateToISO = date =>
getOnlyValidDate(date)
.toISOString()
.substring(0, 10);
// Retrieve the current hour, minute, sec, ms, time into a timestamp
const hours = date => getOnlyValidDate(date).getHours() * 3600 * 1000;
const minutes = date => getOnlyValidDate(date).getMinutes() * 60 * 1000;
const seconds = date => getOnlyValidDate(date).getSeconds() * 1000;
const ms = date => getOnlyValidDate(date).getMilliseconds();
const time = date => hours(getOnlyValidDate(date)) + minutes(getOnlyValidDate(date)) + seconds(getOnlyValidDate(date)) + ms(getOnlyValidDate(date));
const time = date =>
hours(getOnlyValidDate(date)) +
minutes(getOnlyValidDate(date)) +
seconds(getOnlyValidDate(date)) +
ms(getOnlyValidDate(date));
// Retrieve the time part from a timestamp and remove the day. Return a int.
const timeMinusDate = date => time(getOnlyValidDate(date));
// Retrieve the day and remove the time. Return a Date
const dateMinusTime = date => new Date(getOnlyValidDate(date).getTime() - time(getOnlyValidDate(date)));
const dateMinusTime = date =>
new Date(getOnlyValidDate(date).getTime() - time(getOnlyValidDate(date)));
const DragHandle = sortableHandle(({ children }) => (
<span className="input-group-text indexNumber">{children}</span>
@ -558,7 +566,9 @@ class CreateElection extends Component {
this.setState({
finish: new Date(
timeMinusDate(finish) +
new Date(getOnlyValidDate(e.target.valueAsNumber)).getTime()
new Date(
getOnlyValidDate(e.target.valueAsNumber)
).getTime()
)
});
}}

72
src/components/views/CreateSuccess.jsx

@ -1,15 +1,16 @@
import React, {Component} from 'react';
import {Col, Container, Row} from 'reactstrap';
import {Link} from 'react-router-dom';
import {withTranslation, Trans} from 'react-i18next';
import {faCopy, faUsers, faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import logoLine from '../../logos/logo-line-white.svg';
import {AppContext} from '../../AppContext';
import CopyField from '../CopyField';
import React, { Component } from "react";
import { Col, Container, Row } from "reactstrap";
import { Link } from "react-router-dom";
import { withTranslation, Trans } from "react-i18next";
import {
faCopy,
faUsers,
faExclamationTriangle
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import logoLine from "../../logos/logo-line-white.svg";
import { AppContext } from "../../AppContext";
import CopyField from "../CopyField";
class CreateSuccess extends Component {
static contextType = AppContext;
@ -17,10 +18,8 @@ class CreateSuccess extends Component {
super(props);
const electionSlug = this.props.match.params.slug;
this.state = {
urlOfVote:
window.location.origin + '/vote/' + electionSlug,
urlOfResult:
window.location.origin + '/result/' + electionSlug,
urlOfVote: window.location.origin + "/vote/" + electionSlug,
urlOfResult: window.location.origin + "/result/" + electionSlug
};
this.urlVoteField = React.createRef();
this.urlResultField = React.createRef();
@ -30,27 +29,25 @@ class CreateSuccess extends Component {
const input = this.urlResultField.current;
input.focus();
input.select();
document.execCommand('copy');
document.execCommand("copy");
};
render() {
const {t} = this.props;
const { t } = this.props;
const electionLink = this.props.invitationOnly ? (
<>
<p className="mt-4 mb-1">
{t('Voters received a link to vote by email. Each link can be used only once!')}
{t(
"Voters received a link to vote by email. Each link can be used only once!"
)}
</p>
</>
) : (
<>
<p className="mt-4 mb-1">
{t('You can now share the election link to participants:')}
{t("You can now share the election link to participants:")}
</p>
<CopyField
value={this.state.urlOfVote}
icon={faCopy}
t={t}
/>
<CopyField value={this.state.urlOfVote} icon={faCopy} t={t} />
</>
);
@ -63,27 +60,25 @@ class CreateSuccess extends Component {
</Row>
<Row className="mt-4">
<Col className="text-center offset-lg-3" lg="6">
<h2>{t('Successful election creation!')}</h2>
<h2>{t("Successful election creation!")}</h2>
{electionLink}
<p className="mt-4 mb-1">
{t('Here is the link for the results in real time:')}
{t("Here is the link for the results in real time:")}
</p>
<CopyField
value={this.state.urlOfResult}
icon={faCopy}
t={t}
/>
<CopyField value={this.state.urlOfResult} icon={faCopy} t={t} />
</Col>
</Row>
<Row className="mt-4 mb-4">
<Col className="text-center offset-lg-3" lg="6">
<div className=" bg-danger text-white p-2 ">
<h4 className="m-0 p-0 text-center">
<FontAwesomeIcon icon={faExclamationTriangle} className="mr-2" />
{t('Keep these links carefully')}
<FontAwesomeIcon
icon={faExclamationTriangle}
className="mr-2"
/>
{t("Keep these links carefully")}
</h4>
<p className="small m-2 p-0">
<Trans i18nKey="t">
@ -99,10 +94,11 @@ class CreateSuccess extends Component {
<Row className="mt-4 mb-4">
<Col className="text-center">
<Link
to={'/vote/' + this.props.match.params.slug}
className="btn btn-success">
to={"/vote/" + this.props.match.params.slug}
className="btn btn-success"
>
<FontAwesomeIcon icon={faUsers} className="mr-2" />
{t('Participate now!')}
{t("Participate now!")}
</Link>
</Col>
</Row>

12
src/components/views/Home.jsx

@ -1,14 +1,12 @@
import React, { Component } from "react";
import { withTranslation } from 'react-i18next';
import { withTranslation } from "react-i18next";
import { Container, Row, Col, Button, Input } from "reactstrap";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faRocket } from "@fortawesome/free-solid-svg-icons";
import { Redirect } from "react-router-dom";
import logoLine from "../../logos/logo-line-white.svg";
class Home extends Component {
constructor(props) {
super(props);
this.state = {
@ -29,7 +27,7 @@ class Home extends Component {
};
render() {
const {t} = this.props;
const { t } = this.props;
const redirect = this.state.redirect;
if (redirect) {
@ -53,7 +51,9 @@ class Home extends Component {
<Row>
<Col className="text-center">
<h3>
{t("Simple and free: organize an election with Majority Judgment.")}
{t(
"Simple and free: organize an election with Majority Judgment."
)}
</h3>
</Col>
</Row>
@ -77,7 +77,7 @@ class Home extends Component {
className="btn btn-block btn-secondary mt-2"
>
<FontAwesomeIcon icon={faRocket} className="mr-2" />
{t("Start")}
{t("Start")}
</Button>
</Col>
</Row>

202
src/components/views/Result.jsx

@ -1,6 +1,6 @@
import React, {Component} from 'react';
import {withTranslation} from 'react-i18next';
import {resolve} from 'url';
import React, { Component } from "react";
import { withTranslation } from "react-i18next";
import { resolve } from "url";
import {
Container,
Row,
@ -9,12 +9,11 @@ import {
Card,
CardHeader,
CardBody,
Table,
} from 'reactstrap';
import {i18nGrades} from '../../Util';
import {AppContext} from '../../AppContext';
import {errorMessage, Error} from '../../Errors';
Table
} from "reactstrap";
import { i18nGrades } from "../../Util";
import { AppContext } from "../../AppContext";
import { errorMessage, Error } from "../../Errors";
class Result extends Component {
static contextType = AppContext;
@ -34,7 +33,7 @@ class Result extends Component {
collapseGraphics: false,
collapseProfiles: false,
electionGrades: i18nGrades(),
errorMessage: '',
errorMessage: ""
};
}
@ -42,7 +41,7 @@ class Result extends Component {
if (!response.ok) {
response.json().then(response => {
this.setState(state => ({
errorMessage: errorMessage(response, this.props.t),
errorMessage: errorMessage(response, this.props.t)
}));
});
throw Error(response);
@ -55,22 +54,22 @@ class Result extends Component {
id: c.id,
name: c.name,
profile: c.profile,
grade: c.grade,
grade: c.grade
}));
this.setState(state => ({candidates: candidates}));
this.setState(state => ({ candidates: candidates }));
return response;
};
detailsToState = response => {
const numGrades = response.num_grades;
const colSizeGradeLg = Math.floor(
(12 - this.state.colSizeCandidateLg) / numGrades,
(12 - this.state.colSizeCandidateLg) / numGrades
);
const colSizeGradeMd = Math.floor(
(12 - this.state.colSizeCandidateMd) / numGrades,
(12 - this.state.colSizeCandidateMd) / numGrades
);
const colSizeGradeXs = Math.floor(
(12 - this.state.colSizeCandidateXs) / numGrades,
(12 - this.state.colSizeCandidateXs) / numGrades
);
this.setState(state => ({
title: response.title,
@ -90,7 +89,7 @@ class Result extends Component {
12 - colSizeGradeXs * numGrades > 0
? 12 - colSizeGradeXs * numGrades
: 12,
electionGrades: i18nGrades().slice(0, numGrades),
electionGrades: i18nGrades().slice(0, numGrades)
}));
return response;
};
@ -98,38 +97,38 @@ class Result extends Component {
componentDidMount() {
// get details of the election
const electionSlug = this.props.match.params.slug;
if (electionSlug === 'dev') {
if (electionSlug === "dev") {
const dataTest = [
{
name: 'BB',
name: "BB",
id: 1,
score: 1.0,
profile: [1, 1, 0, 0, 0, 0, 0],
grade: 1,
grade: 1
},
{
name: 'CC',
name: "CC",
id: 2,
score: 1.0,
profile: [0, 0, 2, 0, 0, 0, 0],
grade: 2,
grade: 2
},
{
name: 'AA',
name: "AA",
id: 0,
score: 1.0,
profile: [1, 1, 0, 0, 0, 0, 0],
grade: 1,
},
grade: 1
}
];
this.setState({candidates: dataTest});
this.setState({ candidates: dataTest });
} else {
const detailsEndpoint = resolve(
this.context.urlServer,
this.context.routesServer.getElection.replace(
new RegExp(':slug', 'g'),
electionSlug,
),
new RegExp(":slug", "g"),
electionSlug
)
);
fetch(detailsEndpoint)
@ -142,9 +141,9 @@ class Result extends Component {
const resultsEndpoint = resolve(
this.context.urlServer,
this.context.routesServer.getResultsElection.replace(
new RegExp(':slug', 'g'),
electionSlug,
),
new RegExp(":slug", "g"),
electionSlug
)
);
fetch(resultsEndpoint)
@ -156,21 +155,20 @@ class Result extends Component {
}
toggleGraphics = () => {
this.setState(state => ({collapseGraphics: !state.collapseGraphics}));
this.setState(state => ({ collapseGraphics: !state.collapseGraphics }));
};
toggleProfiles = () => {
this.setState(state => ({collapseProfiles: !state.collapseProfiles}));
this.setState(state => ({ collapseProfiles: !state.collapseProfiles }));
};
render() {
const {errorMessage, candidates, electionGrades} = this.state;
const {t} = this.props;
const { errorMessage, candidates, electionGrades } = this.state;
const { t } = this.props;
const i18nGradesObject = i18nGrades();
const offsetGrade = i18nGradesObject.length-(this.state.numGrades);
const offsetGrade = i18nGradesObject.length - this.state.numGrades;
if (errorMessage && errorMessage !== '') {
if (errorMessage && errorMessage !== "") {
return <Error value={errorMessage} />;
}
@ -192,26 +190,29 @@ class Result extends Component {
<Row className="mt-5">
<Col>
<h1>{t('Results of the election:')}</h1>
<h1>{t("Results of the election:")}</h1>
<h5>
<small>
{t('Number of votes:')}
{' ' + numVotes}
{t("Number of votes:")}
{" " + numVotes}
</small>
</h5>
<hr className="mb-5" />
<ol>
{candidates.map((candidate, i) => {
const gradeValue=candidate.grade+offsetGrade;
const gradeValue = candidate.grade + offsetGrade;
return (
<li key={i} className="mt-2">
<span className="mt-2 ml-2">{candidate.name}</span>
<span
className="badge badge-light ml-2 mt-2"
style={{
backgroundColor: electionGrades.slice(0).reverse()[(candidate.grade)].color,
color: '#fff',
}}>
backgroundColor: electionGrades.slice(0).reverse()[
candidate.grade
].color,
color: "#fff"
}}
>
{i18nGradesObject.slice(0).reverse()[gradeValue].label}
</span>
{/* <span className="badge badge-dark mt-2 ml-2">
@ -230,10 +231,11 @@ class Result extends Component {
<CardHeader className="pointer" onClick={this.toggleGraphics}>
<h4
className={
'm-0 panel-title ' +
(this.state.collapseGraphics ? 'collapsed' : '')
}>
{t('Graph')}
"m-0 panel-title " +
(this.state.collapseGraphics ? "collapsed" : "")
}
>
{t("Graph")}
</h4>
</CardHeader>
<Collapse isOpen={this.state.collapseGraphics}>
@ -241,42 +243,46 @@ class Result extends Component {
<div>
<div
className="median"
style={{height: candidates.length * 28 + 30}}
style={{ height: candidates.length * 28 + 30 }}
/>
<table style={{width: '100%'}}>
<table style={{ width: "100%" }}>
<tbody>
{candidates.map((candidate, i) => {
return (
<tr key={i}>
<td style={{width: '30px'}}>{i + 1}</td>
<td style={{ width: "30px" }}>{i + 1}</td>
{/*candidate.label*/}
<td>
<table style={{width: '100%'}}>
<table style={{ width: "100%" }}>
<tbody>
<tr>
{gradeIds.slice(0).reverse().map((id, i) => {
const value = candidate.profile[id];
if (value > 0) {
let percent =
(value * 100) / numVotes + '%';
if (i === 0) {
percent = 'auto';
{gradeIds
.slice(0)
.reverse()
.map((id, i) => {
const value = candidate.profile[id];
if (value > 0) {
let percent =
(value * 100) / numVotes + "%";
if (i === 0) {
percent = "auto";
}
return (
<td
key={i}
style={{
width: percent,
backgroundColor: this.state
.electionGrades[i].color
}}
>
&nbsp;
</td>
);
} else {
return null;
}
return (
<td
key={i}
style={{
width: percent,
backgroundColor: this.state
.electionGrades[i].color,
}}>
&nbsp;
</td>
);
} else {
return null;
}
})}
})}
</tr>
</tbody>
</table>
@ -292,7 +298,7 @@ class Result extends Component {
{candidates.map((candidate, i) => {
return (
<span key={i}>
{i > 0 ? ', ' : ''}
{i > 0 ? ", " : ""}
<b>{i + 1}</b>: {candidate.name}
</span>
);
@ -308,8 +314,9 @@ class Result extends Component {
className="badge badge-light mr-2 mt-2"
style={{
backgroundColor: grade.color,
color: '#fff',
}}>
color: "#fff"
}}
>
{grade.label}
</span>
);
@ -327,10 +334,11 @@ class Result extends Component {
<CardHeader className="pointer" onClick={this.toggleProfiles}>
<h4
className={
'm-0 panel-title ' +
(this.state.collapseProfiles ? 'collapsed' : '')
}>
{t('Preference profile')}
"m-0 panel-title " +
(this.state.collapseProfiles ? "collapsed" : "")
}
>
{t("Preference profile")}
</h4>
</CardHeader>
<Collapse isOpen={this.state.collapseProfiles}>
@ -347,9 +355,10 @@ class Result extends Component {
className="badge badge-light"
style={{
backgroundColor: grade.color,
color: '#fff',
}}>
{grade.label}{' '}
color: "#fff"
}}
>
{grade.label}{" "}
</span>
</th>
);
@ -361,14 +370,17 @@ class Result extends Component {
return (
<tr key={i}>
<td>{i + 1}</td>
{gradeIds.slice(0).reverse().map((id, i) => {
const value = candidate.profile[id];
const percent = (
(value / numVotes) *
100
).toFixed(1);
return <td key={i}>{percent} %</td>;
})}
{gradeIds
.slice(0)
.reverse()
.map((id, i) => {
const value = candidate.profile[id];
const percent = (
(value / numVotes) *
100
).toFixed(1);
return <td key={i}>{percent} %</td>;
})}
</tr>
);
})}
@ -379,7 +391,7 @@ class Result extends Component {
{candidates.map((candidate, i) => {
return (
<span key={i}>
{i > 0 ? ', ' : ''}
{i > 0 ? ", " : ""}
<b>{i + 1}</b>: {candidate.name}
</span>
);

10
src/components/views/UnknownElection.jsx

@ -13,7 +13,7 @@ class UnknownElection extends Component {
}
render() {
const {t} = this.props;
const { t } = this.props;
return (
<Container>
<Row>
@ -23,14 +23,18 @@ class UnknownElection extends Component {
</Row>
<Row className="mt-4">
<Col className="text-center">
<h2>{t("Oops! This election does not exist or it is not available anymore.")}</h2>
<h2>
{t(
"Oops! This election does not exist or it is not available anymore."
)}
</h2>
<p>{t("You can start another election.")}</p>
</Col>
</Row>
<Row className="mt-4">
<Col className="text-center">
<Link to="/" className="btn btn-secondary">
{t("Go back to homepage")}
{t("Go back to homepage")}
</Link>
</Col>
</Row>

168
src/components/views/Vote.jsx

@ -1,14 +1,14 @@
import React, {Component} from 'react';
import {Redirect} from 'react-router-dom';
import {withTranslation} from 'react-i18next';
import {Button, Col, Container, Row} from 'reactstrap';
import {toast, ToastContainer} from 'react-toastify';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCheck} from '@fortawesome/free-solid-svg-icons';
import {resolve} from 'url';
import {i18nGrades} from '../../Util';
import {AppContext} from '../../AppContext';
import {errorMessage} from '../../Errors';
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
import { withTranslation } from "react-i18next";
import { Button, Col, Container, Row } from "reactstrap";
import { toast, ToastContainer } from "react-toastify";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck } from "@fortawesome/free-solid-svg-icons";
import { resolve } from "url";
import { i18nGrades } from "../../Util";
import { AppContext } from "../../AppContext";
import { errorMessage } from "../../Errors";
const shuffle = array => array.sort(() => Math.random() - 0.5);
@ -29,7 +29,7 @@ class Vote extends Component {
colSizeGradeXs: 1,
redirectTo: null,
electionGrades: i18nGrades(),
errorMsg: "",
errorMsg: ""
};
}
@ -37,9 +37,9 @@ class Vote extends Component {
if (!response.ok) {
response.json().then(response => {
console.log(response);
const {t} = this.props;
const { t } = this.props;
this.setState(state => ({
errorMsg: errorMessage(response, t)
errorMsg: errorMessage(response, t)
}));
});
throw Error(response);
@ -51,18 +51,18 @@ class Vote extends Component {
const numGrades = response.num_grades;
const candidates = response.candidates.map((c, i) => ({
id: i,
label: c,
label: c
}));
shuffle(candidates);
const colSizeGradeLg = Math.floor(
(12 - this.state.colSizeCandidateLg) / numGrades,
(12 - this.state.colSizeCandidateLg) / numGrades
);
const colSizeGradeMd = Math.floor(
(12 - this.state.colSizeCandidateMd) / numGrades,
(12 - this.state.colSizeCandidateMd) / numGrades
);
const colSizeGradeXs = Math.floor(
(12 - this.state.colSizeCandidateXs) / numGrades,
(12 - this.state.colSizeCandidateXs) / numGrades
);
this.setState(state => ({
@ -83,7 +83,7 @@ class Vote extends Component {
colSizeCandidateXs:
12 - colSizeGradeXs * numGrades > 0
? 12 - colSizeGradeXs * numGrades
: 12,
: 12
}));
return response;
};
@ -94,9 +94,9 @@ class Vote extends Component {
const detailsEndpoint = resolve(
this.context.urlServer,
this.context.routesServer.getElection.replace(
new RegExp(':slug', 'g'),
electionSlug,
),
new RegExp(":slug", "g"),
electionSlug
)
);
fetch(detailsEndpoint)
.then(this.handleErrors)
@ -107,33 +107,33 @@ class Vote extends Component {
handleGradeClick = event => {
let data = {
id: parseInt(event.currentTarget.getAttribute('data-id')),
value: parseInt(event.currentTarget.value),
id: parseInt(event.currentTarget.getAttribute("data-id")),
value: parseInt(event.currentTarget.value)
};
//remove candidate
let ratedCandidates = this.state.ratedCandidates.filter(
ratedCandidate => ratedCandidate.id !== data.id,
ratedCandidate => ratedCandidate.id !== data.id
);
ratedCandidates.push(data);
this.setState({ratedCandidates});
this.setState({ ratedCandidates });
};
handleSubmitWithoutAllRate = () => {
const {t} = this.props;
toast.error(t('You have to judge every candidate/proposal!'), {
position: toast.POSITION.TOP_CENTER,
const { t } = this.props;
toast.error(t("You have to judge every candidate/proposal!"), {
position: toast.POSITION.TOP_CENTER
});
};
handleSubmit = event => {
event.preventDefault();
const {ratedCandidates} = this.state;
const { ratedCandidates } = this.state;
const electionSlug = this.props.match.params.slug;
const token = this.props.location.search.substr(7);
const endpoint = resolve(
this.context.urlServer,
this.context.routesServer.voteElection,
this.context.routesServer.voteElection
);
const gradesById = {};
@ -147,29 +147,29 @@ class Vote extends Component {
const payload = {
election: electionSlug,
grades_by_candidate: gradesByCandidate,
grades_by_candidate: gradesByCandidate
};
if (token !== '') {
payload['token'] = token;
if (token !== "") {
payload["token"] = token;
}
fetch(endpoint, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload),
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
})
.then(this.handleErrors)
.then(result =>
this.setState({redirectTo: '/vote-success/' + electionSlug}),
this.setState({ redirectTo: "/vote-success/" + electionSlug })
)
.catch(error => error);
};
render() {
const {t} = this.props;
const {candidates, errorMsg, redirectTo} = this.state;
const { t } = this.props;
const { candidates, errorMsg, redirectTo } = this.state;
const grades = i18nGrades();
const offsetGrade = grades.length-this.state.numGrades;
const offsetGrade = grades.length - this.state.numGrades;
const electionGrades = grades.slice(0, this.state.numGrades);
if (redirectTo) {
@ -177,15 +177,15 @@ class Vote extends Component {
}
if (errorMsg !== "") {
return(
<Container>
return (
<Container>
<Row>
<Col>
<h3>{errorMsg}</h3>
</Col>
</Row>
</Container>
);
</Container>
);
}
return (
@ -201,7 +201,8 @@ class Vote extends Component {
<Col
xs={this.state.colSizeCandidateXs}
md={this.state.colSizeCandidateMd}
lg={this.state.colSizeCandidateLg}>
lg={this.state.colSizeCandidateLg}
>
<h5>&nbsp;</h5>
</Col>
{electionGrades.map((grade, gradeId) => {
@ -212,10 +213,12 @@ class Vote extends Component {
lg={this.state.colSizeGradeLg}
key={gradeId}
className="text-center p-0"
style={{lineHeight: 2}}>
style={{ lineHeight: 2 }}
>
<small
className="nowrap bold badge"
style={{backgroundColor: grade.color, color: '#fff'}}>
style={{ backgroundColor: grade.color, color: "#fff" }}
>
{grade.label}
</small>
</Col>
@ -229,81 +232,95 @@ class Vote extends Component {
<Col
xs={this.state.colSizeCandidateXs}
md={this.state.colSizeCandidateMd}
lg={this.state.colSizeCandidateLg}>
lg={this.state.colSizeCandidateLg}
>
<h5 className="m-0">{candidate.label}</h5>
<hr className="d-lg-none" />
</Col>
{electionGrades.map((grade, gradeId) => {
console.assert(gradeId < this.state.numGrades)
const gradeValue = grade.value-offsetGrade;
console.assert(gradeId < this.state.numGrades);
const gradeValue = grade.value - offsetGrade;
return (
<Col
xs={this.state.colSizeGradeXs}
md={this.state.colSizeGradeMd}
lg={this.state.colSizeGradeLg}
key={gradeId}
className="text-lg-center">
className="text-lg-center"
>
<label
htmlFor={'candidateGrade' + candidateId + '-' + gradeValue}
className="check">
htmlFor={
"candidateGrade" + candidateId + "-" + gradeValue
}
className="check"
>