pull/80/head
nicgirault 2 years ago
parent 4c2759d8c9
commit ba0ae5dfc8

@ -1,19 +1,65 @@
version: 2.1
workflows:
workflow:
jobs:
- test:
name: test
- deploy:
name: deploy
filters:
branches:
only:
- next
requires:
- test
jobs:
build:
test:
docker:
- image: cimg/node:lts
steps:
- checkout
- restore_cache:
name: Restore Yarn Package Cache
keys:
- node_modules-{{ checksum "yarn.lock" }}
- run:
name: update-yarn
command: 'curl --compressed -o- -L https://yarnpkg.com/install.sh | bash'
- run:
name: install-dependencies
command: yarn install
- run:
name: compile-translations
command: yarn translate
name: Install Dependencies
command: yarn install --frozen-lockfile
- save_cache:
name: Save Yarn Package Cache
key: node_modules-{{ checksum "yarn.lock" }}
paths:
- node_modules
- ~/.cache
background: true
- run:
name: test
command: yarn test
deploy:
docker:
- image: cimg/node:lts
steps:
- checkout
- restore_cache:
name: Restore Yarn Package Cache
keys:
- node_modules-{{ checksum "yarn.lock" }}
- run:
name: Install Dependencies
command: yarn install --frozen-lockfile
- save_cache:
name: Save Yarn Package Cache
key: node_modules-{{ checksum "yarn.lock" }}
paths:
- node_modules
- ~/.cache
background: true
- run:
name: build
command: yarn build
- run:
name: deploy
command: yarn deploy

@ -1,35 +0,0 @@
{
"env": {
"browser": true,
"es6": true,
"node": true,
"commonjs": true
},
"parser": "babel-eslint",
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": [
"react"
],
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"no-console": "off"
}
}

19
.gitignore vendored

@ -8,29 +8,16 @@
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Local Netlify folder
.netlify
functions/next_*
.env
npm-debug.log*
yarn-debug.log*
yarn-error.log*

1239
LICENSE

File diff suppressed because it is too large Load Diff

@ -1,28 +0,0 @@
# Usage: $ make
NPM := $(shell eval command -v npm)
APT := $(shell eval command -v apt)
.PHONY: help
help: ## Usage: make <concept>, eg: make install
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
install: ## Install required javascript dependencies
ifndef NPM
ifdef APT
@echo "Installing NPM debian package…"
sudo apt install -y npm
endif
endif
npm install
demo: ## Run locally at http://localhost:3000
npm run dev
love: ## Fund development of Majority Judgment
firefox https://www.paypal.com/donate/?hosted_button_id=QD6U4D323WV4S

@ -4,7 +4,6 @@
[![Netlify Status](https://api.netlify.com/api/v1/badges/021c39c6-1018-4e3f-98e2-f808b4ea8f6d/deploy-status)](https://app.netlify.com/sites/epic-nightingale-99f910/deploys)
[![Join the Discord chat at https://discord.gg/rAAQG9S](https://img.shields.io/discord/705322981102190593.svg)](https://discord.gg/rAAQG9S)
:ballot_box: This project is going to be the default front-end for our [election application](https://app.mieuxvoter.fr).
:computer: It is connected to our [back-end](https://github.com/MieuxVoter/mv-api-server-apiplatform). The back-end is used for storing the votes and computing the majority judgment ranking. You can use our back-end free of charge, but you can also start your own instance of the back-end using our Dockerfiles.
@ -13,12 +12,9 @@
:world_map: The front-end stores its own translations. See below how you can edit them easily.
## :paintbrush: Customize your own application
The separation between the front-end and the back-end makes it easy to customize your own application. Just install
The separation between the front-end and the back-end makes it easy to customize your own application. Just install
## :gear: Install options
@ -26,12 +22,11 @@ The separation between the front-end and the back-end makes it easy to customize
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/MieuxVoter/mv-front-react&utm_source=github)
**Option two:** Manual clone
1. Clone this repo: `git clone https://github.com/MieuxVoter/mv-front-nextjs.git`
2. Navigate to the directory and install dependencies: `npm install` or `make`
3. Start a local server: `npm run dev` and open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
3. Start a local server: `npm run dev` and open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
4. Make your changes
5. Deploy your project.
@ -44,7 +39,7 @@ If you decide to deploy your project in another way, please fill a pull-request
To add support for mail sending, you need to connect the application with a mailing service. For now, we only support [Mailgun](mailgun.com), which offer very competitive prices. You can fill an issue if you require another mailing service.
To connect your application with Mailgun, you need to add the environment variables to your project:
- `MAILGUN_API_KEY`,
- `MAILGUN_DOMAIN`,
- `MAILGUN_URL`,
@ -53,10 +48,8 @@ To connect your application with Mailgun, you need to add the environment variab
You can add the environment variables on an `.env` file or directly on [Netlify](https://docs.netlify.com/configure-builds/environment-variables/).
## :world_map: I18N at heart
You can directly modified the translation files in the folder `public/locales`.
In case you want to add support for another language, you need as well to add it on `net-i18next.config.js` and on the `LanguageSelector` component.

@ -1,69 +0,0 @@
/* eslint react/prop-types: 0 */
import React from "react";
import { Button } from "reactstrap";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCopy,
faVoteYea,
faExclamationTriangle,
faExternalLinkAlt,
} from "@fortawesome/free-solid-svg-icons";
const CopyField = (props) => {
const ref = React.createRef();
const handleClickOnField = (event) => {
event.target.focus();
event.target.select();
};
const handleClickOnButton = () => {
const input = ref.current;
input.focus();
input.select();
document.execCommand("copy");
};
const { t, value, iconCopy, iconOpen } = props;
return (
<div className="input-group ">
<input
type="text"
className="form-control"
ref={ref}
value={value}
readOnly
onClick={handleClickOnField}
/>
<div className="input-group-append">
{/*
<Button
href={value}
target="_blank"
rel="noreferrer"
className="btn btn-success"
type="button"
>
<FontAwesomeIcon icon={iconOpen} className="mr-2" />
{t("Go")}
</Button>
*/}
<Button
className="btn btn-secondary"
onClick={handleClickOnButton}
type="button"
>
<FontAwesomeIcon icon={iconCopy} className="mr-2" />
{t("Copy")}
</Button>
</div>
</div>
);
};
CopyField.defaultProps = {
iconCopy: faCopy,
iconOpen: faExternalLinkAlt,
};
export default CopyField;

@ -1,40 +0,0 @@
import Link from "next/link";
import { Container, Row, Col } from "reactstrap";
import { useTranslation } from "next-i18next";
const Error = (props) => {
const { t } = useTranslation();
return (
<Container>
<Row>
<Link href="/">
<a className="d-block ml-auto mr-auto mb-4">
<img src="/logos/logo-line-white.svg" alt="logo" height="128" />
</a>
</Link>
</Row>
<Row className="mt-4">
<Col className="text-center">
<h4>{props.value}</h4>
</Col>
</Row>
<Row className="mt-4">
<Col className="text-right mr-4">
<Link href="/">
<a className="btn btn-secondary">{t("common.backHomepage")}</a>
</Link>
</Col>
<Col className="text-left ml-4">
<a
href="mailto:app@mieuxvoter.fr?subject=[HELP]"
className="btn btn-success"
>
{t("resource.help")}
</a>
</Col>
</Row>
</Container>
);
};
export default Error;

@ -1,29 +0,0 @@
/* eslint react/prop-types: 0 */
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faFacebookSquare } from "@fortawesome/free-brands-svg-icons";
const Facebook = props => {
const handleClick = () => {
const url =
"https://www.facebook.com/sharer.php?u=" +
props.url +
"&t=" +
props.title;
window.open(
url,
"",
"menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=500,width=700"
);
};
return (
<button className={props.className} onClick={handleClick} type="button">
<FontAwesomeIcon icon={faFacebookSquare} className="mr-2" />
{props.text}
</button>
);
};
export default Facebook;
//i

@ -1,25 +0,0 @@
import PropTypes from 'prop-types';
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faCommentAlt} from "@fortawesome/free-solid-svg-icons";
import {api} from "@services/api"
const Gform = (props) => {
return (
<a
className={props.className}
href={api.feedbackForm}
target="_blank"
rel="noopener noreferrer"
>
<FontAwesomeIcon icon={faCommentAlt} className="mr-2" />
Votre avis nous intéresse !
</a>
);
}
Gform.propTypes = {
className: PropTypes.string,
};
export default Gform;

@ -1,24 +0,0 @@
/* eslint react/prop-types: 0 */
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";
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;

@ -1,44 +0,0 @@
import {useTranslation} from "next-i18next";
import {useRouter} from "next/router"
import {faPaypal} from "@fortawesome/free-brands-svg-icons";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
const Paypal = () => {
const {t} = useTranslation();
// FIXME generate a xx_XX string for locale version
const {locale} = useRouter();
let localeShort = locale.substring(0, 2);
let localeComplete =
localeShort.toLowerCase() + "_" + localeShort.toUpperCase();
if (localeComplete === "en_EN") {
localeComplete = "en_US";
}
const pixelLink =
`https://www.paypal.com/${localeComplete}/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 btn-primary"
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 Paypal;

@ -1,4 +0,0 @@
import * as React from "react";
import FlagIconFactory from "react-flag-icon-css";
export const FlagIcon = FlagIconFactory(React, { useCssModules: false });

@ -1,57 +0,0 @@
import {useState} from "react";
import {
faTrashAlt,
} from "@fortawesome/free-solid-svg-icons";
import {Button, Modal, ModalHeader, ModalBody, ModalFooter} from "reactstrap";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {useTranslation} from "next-i18next";
const ButtonWithConfirm = ({className, label, onDelete}) => {
const [visibled, setVisibility] = useState(false);
const {t} = useTranslation();
const toggle = () => setVisibility(!visibled)
return (
<div className="input-group-append">
<button
type="button"
className={className}
onClick={toggle}
>
<FontAwesomeIcon icon={faTrashAlt} />
</button>
<Modal
isOpen={visibled}
toggle={toggle}
className="modal-dialog-centered"
>
<ModalHeader toggle={toggle}>{t("Delete?")}</ModalHeader>
<ModalBody>
{t("Are you sure to delete")}{" "}
{label && label !== "" ? (
<b>&quot;{label}&quot;</b>
) : (
<>{t("the row")}</>
)}{" "}
?
</ModalBody>
<ModalFooter>
<Button
color="primary-outline"
className="text-primary border-primary"
onClick={toggle}>
{t("No")}
</Button>
<Button color="primary"
onClick={() => {toggle(); onDelete();}}
>
{t("Yes")}
</Button>
</ModalFooter>
</Modal>
</div >
);
}
export default ButtonWithConfirm;

@ -1,55 +0,0 @@
import {useState} from 'react'
import ButtonWithConfirm from "./ButtonWithConfirm";
import {
Row,
Col,
Input,
InputGroup,
InputGroupAddon,
} from "reactstrap";
import {useTranslation} from "react-i18next";
import {
sortableHandle
} from "react-sortable-hoc";
import HelpButton from "@components/form/HelpButton";
const DragHandle = sortableHandle(({children}) => (
<span className="input-group-text indexNumber">{children}</span>
));
const CandidateField = ({label, candIndex, onDelete, ...inputProps}) => {
const {t} = useTranslation();
return (
<Row>
<Col>
<InputGroup>
<InputGroupAddon addonType="prepend">
<DragHandle>
<span>{candIndex + 1}</span>
</DragHandle>
</InputGroupAddon>
<Input
type="text"
value={label}
{...inputProps}
placeholder={t("resource.candidatePlaceholder")}
tabIndex={candIndex + 1}
maxLength="250"
autoFocus
/>
<ButtonWithConfirm className="btn btn-primary border-light" label={label} onDelete={onDelete}>
</ButtonWithConfirm>
</InputGroup>
</Col>
<Col xs="auto" className="align-self-center pl-0">
<HelpButton>
{t(
"Enter the name of your candidate or proposal here (250 characters max.)"
)}
</HelpButton>
</Col>
</Row>
);
}
export default CandidateField

@ -1,128 +0,0 @@
import {useState, useEffect, createRef} from 'react'
import {useTranslation} from "react-i18next";
import {
Button,
Card,
CardBody
} from "reactstrap";
import {
faPlus,
} from "@fortawesome/free-solid-svg-icons";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {
sortableContainer,
sortableElement,
sortableHandle
} from "react-sortable-hoc";
import arrayMove from "array-move"
import CandidateField from './CandidateField'
// const SortableItem = sortableElement(({className, ...childProps}) => <li className={className}><CandidateField {...childProps} /></li>);
//
// const SortableContainer = sortableContainer(({children}) => {
// return <ul className="sortable">{children}</ul>;
// });
const SortableItem = ({className, ...childProps}) => <li className={className}><CandidateField {...childProps} /></li>;
const SortableContainer = ({children}) => {
return <ul className="sortable">{children}</ul>;
};
const CandidatesField = ({onChange}) => {
const {t} = useTranslation();
const [candidates, setCandidates] = useState([])
const addCandidate = () => {
if (candidates.length < 1000) {
candidates.push({label: "", fieldRef: createRef()});
setCandidates([...candidates]);
onChange(candidates)
} else {
console.error("Too many candidates")
}
};
useEffect(() => {
addCandidate();
addCandidate();
}, [])
const removeCandidate = index => {
if (candidates.length === 1) {
const newCandidates = []
newCandidates.push({label: "", fieldRef: createRef()});
newCandidates.push({label: "", fieldRef: createRef()});
setCandidates(newCandidates);
onChange(newCandidates)
}
else {
const newCandidates = candidates.filter((c, i) => i != index)
setCandidates(newCandidates);
onChange(newCandidates);
}
};
const editCandidate = (index, label) => {
candidates[index].label = label
setCandidates([...candidates]);
onChange(candidates);
};
const handleKeyPress = (e, index) => {
if (e.key === "Enter") {
e.preventDefault();
if (index + 1 === candidates.length) {
addCandidate();
}
else {
candidates[index + 1].fieldRef.current.focus();
}
}
}
const onSortEnd = ({oldIndex, newIndex}) => {
setCandidates(arrayMove(candidates, oldIndex, newIndex));
};
return (
<>
<SortableContainer onSortEnd={onSortEnd}>
{candidates.map((candidate, index) => {
const className = "sortable"
return (
<SortableItem
className={className}
key={`item-${index}`}
index={index}
candIndex={index}
label={candidate.label}
onDelete={() => removeCandidate(index)}
onChange={(e) => editCandidate(index, e.target.value)}
onKeyPress={(e) => handleKeyPress(e, index)}
innerRef={candidate.fieldRef}
/>
)
})}
</SortableContainer>
<Button
color="secondary"
className="btn-block mt-2"
tabIndex={candidates.length + 2}
type="button"
onClick={addCandidate}
>
<FontAwesomeIcon icon={faPlus} className="mr-2" />
{t("Add a proposal")}
</Button>
</>
);
}
export default CandidatesField

@ -1,164 +0,0 @@
import {useTranslation} from "next-i18next";
import {useState} from "react";
import {
faExclamationTriangle,
faCheck,
} from "@fortawesome/free-solid-svg-icons";
import {Button, Modal, ModalHeader, ModalBody, ModalFooter} from "reactstrap";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
const ConfirmModal = ({tabIndex, title, candidates, grades, isTimeLimited, start, finish, emails, restrictResult, className, confirmCallback}) => {
const [visibled, setVisibility] = useState(false);
const {t} = useTranslation();
const toggle = () => setVisibility(!visibled)
return (
<div className="input-group-append">
<button
type="button"
className={className}
onClick={toggle}
tabIndex={tabIndex}
>
<FontAwesomeIcon icon={faCheck} className="mr-2" />
{t("Validate")}
</button>
<Modal
isOpen={visibled}
toggle={toggle}
className="modal-dialog-centered"
>
<ModalHeader toggle={toggle}>
{t("Confirm your vote")}
</ModalHeader>
<ModalBody>
<div className="mt-1 mb-1">
<div className="text-white bg-primary p-2 pl-3 pr-3 rounded">
{t("Question of the election")}
</div>
<div className="p-2 pl-3 pr-3 bg-light mb-3">{title}</div>
<div className="text-white bg-primary p-2 pl-3 pr-3 rounded">
{t("Candidates/Proposals")}
</div>
<div className="p-2 pl-3 pr-3 bg-light mb-3">
<ul className="m-0 pl-4">
{candidates.map((candidate, i) => {
if (candidate.label !== "") {
return (
<li key={i} className="m-0">
{candidate.label}
</li>
);
} else {
return <li key={i} className="d-none" />;
}
})}
</ul>
</div>
<div className={(isTimeLimited ? "d-block " : "d-none")} >
<div className="text-white bg-primary p-2 pl-3 pr-3 rounded">
{t("Dates")}
</div>
<div className="p-2 pl-3 pr-3 bg-light mb-3">
{t("The election will take place from")}{" "}
<b>
{start.toLocaleDateString()}, {t("at")}{" "}
{start.toLocaleTimeString()}
</b>{" "}
{t("to")}{" "}
<b>
{finish.toLocaleDateString()}, {t("at")}{" "}
{finish.toLocaleTimeString()}
</b>
</div>
</div>
<div className="text-white bg-primary p-2 pl-3 pr-3 rounded">
{t("Grades")}
</div>
<div className="p-2 pl-3 pr-3 bg-light mb-3">
{grades.map((mention, i) => {
return i < grades.length ? (
<span
key={i}
className="badge badge-light mr-2 mt-2"
style={{
backgroundColor: mention.color,
color: "#fff"
}}
>
{mention.label}
</span>
) : (
<span key={i} />
);
})}
</div>
<div className="text-white bg-primary p-2 pl-3 pr-3 rounded">
{t("Voters' list")}
</div>
<div className="p-2 pl-3 pr-3 bg-light mb-3">
{emails.length > 0 ? (
emails.join(", ")
) : (
<p>
{t("The form contains no address.")}
<br />
<em>
{t(
"The election will be opened to anyone with the link"
)}
</em>
</p>
)}
</div>
{restrictResult ? (
<div>
<div className="small bg-primary text-white p-3 mt-2 rounded">
<h6 className="m-0 p-0">
<FontAwesomeIcon
icon={faExclamationTriangle}
className="mr-2"
/>
<u>{t("Results available at the close of the vote")}</u>
</h6>
<p className="m-2 p-0">
<span>
{t(
"The results page will not be accessible until the end date is reached."
)}{" "}
({finish.toLocaleDateString()} {t("at")}{" "}
{finish.toLocaleTimeString()})
</span>
</p>
</div>
</div>
) : (
<div>
<div className="small bg-primary text-white p-3 mt-2 rounded">
<h6 className="m-0 p-0">
{t("Results available at any time")}
</h6>
</div>
</div>
)}
</div>
</ModalBody>
<ModalFooter>
<Button
color="primary-outline"
className="text-primary border-primary"
onClick={toggle}>
{t("Cancel")}
</Button>
<Button color="primary"
onClick={() => {toggle(); confirmCallback();}}
>
{t("Start the election")}
</Button>
</ModalFooter>
</Modal>
</div >
)
}
export default ConfirmModal

@ -1,75 +0,0 @@
/* eslint react/prop-types: 0 */
import React, { Component } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
class HelpButton extends Component {
constructor(props) {
super(props);
this.state = {
tooltipOpen: false
};
}
showTooltip = () => {
this.setState({
tooltipOpen: true
});
};
hideTooltip = () => {
this.setState({
tooltipOpen: false
});
};
render() {
return (
<span className={this.props.className}>
<span>
{this.state.tooltipOpen ? (
<span
style={{
position: "absolute",
zIndex: 10,
fontSize: "12px",
color: "#000",
backgroundColor: "#fff",
display: "inline-block",
borderRadius: "0.25rem",
boxShadow: "-5px 0 5px rgba(0,0,0,0.5)",
maxWidth: "200px",
padding: "10px",
marginLeft: "-215px",
marginTop: "-25px"
}}
>
<span
style={{
position: "absolute",
width: 0,
height: 0,
borderTop: "10px solid transparent",
borderBottom: "10px solid transparent",
borderLeft: "10px solid #fff",
marginLeft: "190px",
marginTop: "15px"
}}
></span>
{this.props.children}
</span>
) : (
<span />
)}
</span>
<FontAwesomeIcon
icon={faQuestionCircle}
onMouseOver={this.showTooltip}
onMouseOut={this.hideTooltip}
/>
</span>
);
}
}
export default HelpButton;

@ -1,94 +0,0 @@
import Link from "next/link";
import { useTranslation } from "next-i18next";
import Paypal from "../banner/Paypal";
import { useBbox } from "./useBbox";
const Footer = () => {
const linkStyle = { whiteSpace: "nowrap" };
const { t } = useTranslation();
const [bboxLink1, link1] = useBbox();
const [bboxLink2, link2] = useBbox();
const [bboxLink3, link3] = useBbox();
const [bboxLink4, link4] = useBbox();
const [bboxLink5, link5] = useBbox();
const [bboxLink6, link6] = useBbox();
const [bboxLink7, link7] = useBbox();
return (
<footer className="text-center">
<div>
<ul className="tacky">
<li
ref={link1}
className={bboxLink1.top === bboxLink2.top ? "" : "no-tack"}
>
<Link href="/" style={linkStyle}>
{t("Homepage")}
</Link>
</li>
<li
ref={link2}
className={bboxLink2.top === bboxLink3.top ? "" : "no-tack"}
>
<Link href="/faq" style={linkStyle}>
{t("FAQ")}
</Link>
</li>
<li
ref={link3}
className={bboxLink3.top === bboxLink4.top ? "" : "no-tack"}
>
<a href="mailto:app@mieuxvoter.fr?subject=[HELP]" style={linkStyle}>
{t("resource.help")}
</a>
</li>
<li
ref={link4}
className={bboxLink4.top === bboxLink5.top ? "" : "no-tack"}
>
<a
href="https://mieuxvoter.fr/"
target="_blank"
rel="noopener noreferrer"
style={linkStyle}
>
{t("Who are we?")}
</a>
</li>
<li
ref={link5}
className={bboxLink5.top === bboxLink6.top ? "" : "no-tack"}
>
<Link href="/privacy-policy" style={linkStyle}>
{t("Privacy policy")}
</Link>
</li>
<li
ref={link6}
className={bboxLink6.top === bboxLink7.top ? "" : "no-tack"}
>
<Link href="/legal-notices" style={linkStyle}>
{t("resource.legalNotices")}
</Link>
</li>
<li ref={link7}>
{" "}
<a
href="https://github.com/MieuxVoter"
target="_blank"
rel="noopener noreferrer"
style={linkStyle}
>
{t("Source code")}
</a>
</li>
</ul>
</div>
<div className="mt-3">
<Paypal btnColor="btn-primary" />
</div>
</footer>
);
};
export default Footer;

@ -1,61 +0,0 @@
/* eslint react/prop-types: 0 */
import {useState} from "react";
import {Collapse, Navbar, NavbarToggler, Nav, NavItem} from "reactstrap";
import Link from "next/link";
import Head from "next/head";
import {useTranslation} from 'next-i18next'
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faRocket} from "@fortawesome/free-solid-svg-icons";
import LanguageSelector from "./LanguageSelector";
const Header = () => {
const [isOpen, setOpen] = useState(false)
const toggle = () => setOpen(!isOpen);
const {t} = useTranslation()
return (
<>
<Head><title>{t("title")}</title></Head>
<header>
<Navbar color="light" light expand="md">
<Link href="/">
<a className="navbar-brand">
<div className="d-flex flex-row">
<div className="align-self-center">
<img src="/logos/logo-color.svg" alt="logo" height="32" />
</div>
<div className="align-self-center ml-2">
<div className="logo-text">
<h1>
{t("Voting platform")}
<small>{t("Majority Judgment")}</small>
</h1>
</div>
</div>
</div>
</a>
</Link>
<NavbarToggler onClick={toggle} />
<Collapse isOpen={isOpen} navbar>
<Nav className="ml-auto" navbar>
<NavItem>
<Link href="/new/">
<a className="text-primary nav-link"> <FontAwesomeIcon icon={faRocket} className="mr-2" />
{t("Start an election")}
</a>
</Link>
</NavItem>
<NavItem style={{width: "80px"}}>
<LanguageSelector />
</NavItem>
</Nav>
</Collapse>
</Navbar>
</header>
</>
);
}
export default Header;

@ -1,32 +0,0 @@
import {useRouter} from 'next/router'
import ReactFlagsSelect from 'react-flags-select';
const LanguageSelector = () => {
const router = useRouter();
let localeShort = router.locale.substring(0, 2).toUpperCase();
if (localeShort === "EN") localeShort = "GB";
const selectHandler = e => {
let locale = e.toLowerCase();
if (locale === "gb") locale = "en";
router.push("", "", {locale})
};
return (
<ReactFlagsSelect
onSelect={selectHandler}
countries={
// ["GB", "FR", "ES", "DE", "RU"]
["GB", "FR"]
}
showOptionLabel={false}
selected={localeShort}
selectedSize={15}
optionsSize={22}
showSelectedLabel={false}
showSecondaryOptionLabel={false}
/>
);
};
export default LanguageSelector;

@ -1,20 +0,0 @@
/* eslint react/prop-types: 0 */
import { useState } from 'react';
import { useRef } from 'react';
import { useEffect } from 'react';
export const useBbox = () => {
const ref = useRef();
const [bbox, setBbox] = useState({});
const set = () =>
setBbox(ref && ref.current ? ref.current.getBoundingClientRect() : {});
useEffect(() => {
set();
window.addEventListener('resize', set);
return () => window.removeEventListener('resize', set);
}, []);
return [bbox, ref];
};

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

@ -1,8 +0,0 @@
import React from "react";
import Loader from "../loader";
const Wait = () => {
return <Loader />;
};
export default Wait;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

@ -1,93 +0,0 @@
'use strict';
const fs = require('fs');
const path = require('path');
const paths = require('./paths');
// Make sure that including paths.js after env.js will read .env variables.
delete require.cache[require.resolve('./paths')];
const NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV) {
throw new Error(
'The NODE_ENV environment variable is required but was not specified.'
);
}
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
var dotenvFiles = [
`${paths.dotenv}.${NODE_ENV}.local`,
`${paths.dotenv}.${NODE_ENV}`,
// Don't include `.env.local` for `test` environment
// since normally you expect tests to produce the same
// results for everyone
NODE_ENV !== 'test' && `${paths.dotenv}.local`,
paths.dotenv,
].filter(Boolean);
// Load environment variables from .env* files. Suppress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set. Variable expansion is supported in .env files.
// https://github.com/motdotla/dotenv
// https://github.com/motdotla/dotenv-expand
dotenvFiles.forEach(dotenvFile => {
if (fs.existsSync(dotenvFile)) {
require('dotenv-expand')(
require('dotenv').config({
path: dotenvFile,
})
);
}
});
// We support resolving modules according to `NODE_PATH`.
// This lets you use absolute paths in imports inside large monorepos:
// https://github.com/facebook/create-react-app/issues/253.
// It works similar to `NODE_PATH` in Node itself:
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
// We also resolve them to make sure all tools using them work consistently.
const appDirectory = fs.realpathSync(process.cwd());
process.env.NODE_PATH = (process.env.NODE_PATH || '')
.split(path.delimiter)
.filter(folder => folder && !path.isAbsolute(folder))
.map(folder => path.resolve(appDirectory, folder))
.join(path.delimiter);
// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
// injected into the application via DefinePlugin in Webpack configuration.
const REACT_APP = /^REACT_APP_/i;
function getClientEnvironment(publicUrl) {
const raw = Object.keys(process.env)
.filter(key => REACT_APP.test(key))
.reduce(
(env, key) => {
env[key] = process.env[key];
return env;
},
{
// Useful for determining whether were running in production mode.
// Most importantly, it switches React into the correct mode.
NODE_ENV: process.env.NODE_ENV || 'development',
// Useful for resolving the correct path to static assets in `public`.
// For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
// This should only be used as an escape hatch. Normally you would put
// images into the `src` and `import` them in code to get their paths.
PUBLIC_URL: publicUrl,
}
);
// Stringify all values so we can feed into Webpack DefinePlugin
const stringified = {
'process.env': Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key]);
return env;
}, {}),
};
return { raw, stringified };
}
module.exports = getClientEnvironment;

@ -1,14 +0,0 @@
'use strict';
// This is a custom Jest transformer turning style imports into empty objects.
// http://facebook.github.io/jest/docs/en/webpack.html
module.exports = {
process() {
return 'module.exports = {};';
},
getCacheKey() {
// The output is always the same.
return 'cssTransform';
},
};

@ -1,40 +0,0 @@
'use strict';
const path = require('path');
const camelcase = require('camelcase');
// This is a custom Jest transformer turning file imports into filenames.
// http://facebook.github.io/jest/docs/en/webpack.html
module.exports = {
process(src, filename) {
const assetFilename = JSON.stringify(path.basename(filename));
if (filename.match(/\.svg$/)) {
// Based on how SVGR generates a component name:
// https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6
const pascalCaseFileName = camelcase(path.parse(filename).name, {
pascalCase: true,
});
const componentName = `Svg${pascalCaseFileName}`;
return `const React = require('react');
module.exports = {
__esModule: true,
default: ${assetFilename},
ReactComponent: React.forwardRef(function ${componentName}(props, ref) {
return {
$$typeof: Symbol.for('react.element'),
type: 'svg',
ref: ref,
key: null,
props: Object.assign({}, props, {
children: ${assetFilename}
})
};
}),
};`;
}
return `module.exports = ${assetFilename};`;
},
};

@ -1,84 +0,0 @@
'use strict';
const fs = require('fs');
const path = require('path');
const paths = require('./paths');
const chalk = require('react-dev-utils/chalk');
/**
* Get the baseUrl of a compilerOptions object.
*
* @param {Object} options
*/
function getAdditionalModulePaths(options = {}) {
const baseUrl = options.baseUrl;
// We need to explicitly check for null and undefined (and not a falsy value) because
// TypeScript treats an empty string as `.`.
if (baseUrl == null) {
// If there's no baseUrl set we respect NODE_PATH
// Note that NODE_PATH is deprecated and will be removed
// in the next major release of create-react-app.
const nodePath = process.env.NODE_PATH || '';
return nodePath.split(path.delimiter).filter(Boolean);
}
const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
// We don't need to do anything if `baseUrl` is set to `node_modules`. This is
// the default behavior.
if (path.relative(paths.appNodeModules, baseUrlResolved) === '') {
return null;
}
// Allow the user set the `baseUrl` to `appSrc`.
if (path.relative(paths.appSrc, baseUrlResolved) === '') {
return [paths.appSrc];
}
// Otherwise, throw an error.
throw new Error(
chalk.red.bold(
"Your project's `baseUrl` can only be set to `src` or `node_modules`." +
' Create React App does not support other values at this time.'
)
);
}
function getModules() {
// Check if TypeScript is setup
const hasTsConfig = fs.existsSync(paths.appTsConfig);
const hasJsConfig = fs.existsSync(paths.appJsConfig);
if (hasTsConfig && hasJsConfig) {
throw new Error(
'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.'
);
}
let config;
// If there's a tsconfig.json we assume it's a
// TypeScript project and set up the config
// based on tsconfig.json
if (hasTsConfig) {
config = require(paths.appTsConfig);
// Otherwise we'll check if there is jsconfig.json
// for non TS projects.
} else if (hasJsConfig) {
config = require(paths.appJsConfig);
}
config = config || {};
const options = config.compilerOptions || {};
const additionalModulePaths = getAdditionalModulePaths(options);
return {
additionalModulePaths: additionalModulePaths,
hasTsConfig,
};
}
module.exports = getModules();

@ -1,90 +0,0 @@
'use strict';
const path = require('path');
const fs = require('fs');
const url = require('url');
// Make sure any symlinks in the project folder are resolved:
// https://github.com/facebook/create-react-app/issues/637
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
const envPublicUrl = process.env.PUBLIC_URL;
function ensureSlash(inputPath, needsSlash) {
const hasSlash = inputPath.endsWith('/');
if (hasSlash && !needsSlash) {
return inputPath.substr(0, inputPath.length - 1);
} else if (!hasSlash && needsSlash) {
return `${inputPath}/`;
} else {
return inputPath;
}
}
const getPublicUrl = appPackageJson =>
envPublicUrl || require(appPackageJson).homepage;
// We use `PUBLIC_URL` environment variable or "homepage" field to infer
// "public path" at which the app is served.
// Webpack needs to know it to put the right <script> hrefs into HTML even in
// single-page apps that may serve index.html for nested URLs like /todos/42.
// We can't use a relative path in HTML because we don't want to load something
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
function getServedPath(appPackageJson) {
const publicUrl = getPublicUrl(appPackageJson);
const servedUrl =
envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');
return ensureSlash(servedUrl, true);
}
const moduleFileExtensions = [
'web.mjs',
'mjs',
'web.js',
'js',
'web.ts',
'ts',
'web.tsx',
'tsx',
'json',
'web.jsx',
'jsx',
];
// Resolve file paths in the same order as webpack
const resolveModule = (resolveFn, filePath) => {
const extension = moduleFileExtensions.find(extension =>
fs.existsSync(resolveFn(`${filePath}.${extension}`))
);
if (extension) {
return resolveFn(`${filePath}.${extension}`);
}
return resolveFn(`${filePath}.js`);
};
// config after eject: we're in ./config/
module.exports = {
dotenv: resolveApp('.env'),
appPath: resolveApp('.'),
appBuild: resolveApp('build'),
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appIndexJs: resolveModule(resolveApp, 'src/index'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
appTsConfig: resolveApp('tsconfig.json'),
appJsConfig: resolveApp('jsconfig.json'),
yarnLockFile: resolveApp('yarn.lock'),
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
proxySetup: resolveApp('src/setupProxy.js'),
appNodeModules: resolveApp('node_modules'),
publicUrl: getPublicUrl(resolveApp('package.json')),
servedPath: getServedPath(resolveApp('package.json')),
};
module.exports.moduleFileExtensions = moduleFileExtensions;

@ -1,35 +0,0 @@
'use strict';
const { resolveModuleName } = require('ts-pnp');
exports.resolveModuleName = (
typescript,
moduleName,
containingFile,
compilerOptions,
resolutionHost
) => {
return resolveModuleName(
moduleName,
containingFile,
compilerOptions,
resolutionHost,
typescript.resolveModuleName
);
};
exports.resolveTypeReferenceDirective = (
typescript,
moduleName,
containingFile,
compilerOptions,
resolutionHost
) => {
return resolveModuleName(
moduleName,
containingFile,
compilerOptions,
resolutionHost,
typescript.resolveTypeReferenceDirective
);
};

@ -1,628 +0,0 @@
'use strict';
const fs = require('fs');
const isWsl = require('is-wsl');
const path = require('path');
const webpack = require('webpack');
const resolve = require('resolve');
const PnpWebpackPlugin = require('pnp-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const safePostCssParser = require('postcss-safe-parser');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const paths = require('./paths');
const modules = require('./modules');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
const postcssNormalize = require('postcss-normalize');
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
// makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
module.exports = function(webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production';
// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
// In development, we always serve from the root. This makes config easier.
const publicPath = isEnvProduction
? paths.servedPath
: isEnvDevelopment && '/';
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseRelativeAssetPaths = publicPath === './';
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
const publicUrl = isEnvProduction
? publicPath.slice(0, -1)
: isEnvDevelopment && '';
// Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl);
// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
loader: MiniCssExtractPlugin.loader,
options: shouldUseRelativeAssetPaths ? { publicPath: '../../' } : {},
},
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
}),
// Adds PostCSS Normalize as the reset css with default options,
// so that it honors browserslist config in package.json
// which in turn let's users customize the target behavior as per their needs.
postcssNormalize(),
],
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
].filter(Boolean);
if (preProcessor) {
loaders.push({
loader: require.resolve(preProcessor),
options: {
sourceMap: isEnvProduction && shouldUseSourceMap,
},
});
}
return loaders;
};
return {
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
// Stop compilation early in production
bail: isEnvProduction,
devtool: isEnvProduction
? shouldUseSourceMap
? 'source-map'
: false
: isEnvDevelopment && 'cheap-module-source-map',
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
entry: [
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code:
paths.appIndexJs,
// We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh.
].filter(Boolean),
output: {
// The build folder.
path: isEnvProduction ? paths.appBuild : undefined,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
filename: isEnvProduction
? 'static/js/[name].[contenthash:8].js'
: isEnvDevelopment && 'static/js/bundle.js',
// TODO: remove this when upgrading to webpack 5
futureEmitAssets: true,
// There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
// We inferred the "public path" (such as / or /my-project) from homepage.
// We use "/" in development.
publicPath: publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction
? info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
},
optimization: {
minimize: isEnvProduction,
minimizer: [
// This is only used in production mode
new TerserPlugin({
terserOptions: {
parse: {
// we want terser to parse ecma 8 code. However, we don't want it
// to apply any minfication steps that turns valid ecma 5 code
// into invalid ecma 5 code. This is why the 'compress' and 'output'
// sections only apply transformations that are ecma 5 safe
// https://github.com/facebook/create-react-app/pull/4234
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
// Disabled because of an issue with Uglify breaking seemingly valid code:
// https://github.com/facebook/create-react-app/issues/2376
// Pending further investigation:
// https://github.com/mishoo/UglifyJS2/issues/2011
comparisons: false,
// Disabled because of an issue with Terser breaking valid code:
// https://github.com/facebook/create-react-app/issues/5250
// Pending futher investigation:
// https://github.com/terser-js/terser/issues/120
inline: 2,
},
mangle: {
safari10: true,
},
output: {
ecma: 5,
comments: false,
// Turned on because emoji and regex is not minified properly using default
// https://github.com/facebook/create-react-app/issues/2488
ascii_only: true,
},
},
// Use multi-process parallel running to improve the build speed
// Default number of concurrent runs: os.cpus().length - 1
// Disabled on WSL (Windows Subsystem for Linux) due to an issue with Terser
// https://github.com/webpack-contrib/terser-webpack-plugin/issues/21
parallel: !isWsl,
// Enable file caching
cache: true,
sourceMap: shouldUseSourceMap,
}),
// This is only used in production mode
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
parser: safePostCssParser,
map: shouldUseSourceMap
? {
// `inline: false` forces the sourcemap to be output into a
// separate file
inline: false,
// `annotation: true` appends the sourceMappingURL to the end of
// the css file, helping the browser find the sourcemap
annotation: true,
}
: false,
},
}),
],
// Automatically split vendor and commons
// https://twitter.com/wSokra/status/969633336732905474
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
splitChunks: {
chunks: 'all',
name: false,
},
// Keep the runtime chunk separated to enable long term caching
// https://twitter.com/wSokra/status/969679223278505985
runtimeChunk: true,
},
resolve: {
// This allows you to set a fallback for where Webpack should look for modules.
// We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebook/create-react-app/issues/253
modules: ['node_modules', paths.appNodeModules].concat(
modules.additionalModulePaths || []
),
// These are the reasonable defaults supported by the Node ecosystem.
// We also include JSX as a common component filename extension to support
// some tools, although we do not recommend using it, see:
// https://github.com/facebook/create-react-app/issues/290
// `web` extension prefixes have been added for better support
// for React Native Web.
extensions: paths.moduleFileExtensions
.map(ext => `.${ext}`)
.filter(ext => useTypeScript || !ext.includes('ts')),
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
},
plugins: [
// Adds support for installing with Plug'n'Play, leading to faster installs and adding
// guards against forgotten dependencies and such.
PnpWebpackPlugin,
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
resolveLoader: {
plugins: [
// Also related to Plug'n'Play, but this time it tells Webpack to load its loaders
// from the current package.
PnpWebpackPlugin.moduleLoader(module),
],
},
module: {
strictExportPresence: true,
rules: [
// Disable require.ensure as it's not a standard language feature.
{ parser: { requireEnsure: false } },
// First, run the linter.
// It's important to do this before Babel processes the JS.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
enforce: 'pre',
use: [
{
options: {
formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'),
},
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrc,
},
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
// "url" loader works like "file" loader except that it embeds assets
// smaller than specified limit in bytes as data URLs to avoid requests.
// A missing `test` is equivalent to a match.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]',
},
},
// Process application JS with Babel.
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
plugins: [
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent: '@svgr/webpack?-svgo,+ref![path]',
},
},
},
],
],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
cacheCompression: isEnvProduction,
compact: isEnvProduction,
},
},
// Process any JS outside of the app with Babel.
// Unlike the application JS, we only compile the standard ES features.
{
test: /\.(js|mjs)$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve('babel-loader'),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [
[
require.resolve('babel-preset-react-app/dependencies'),
{ helpers: true },
],
],
cacheDirectory: true,
cacheCompression: isEnvProduction,
// If an error happens in a package, it's possible to be
// because it was compiled. Thus, we don't want the browser
// debugger to show the original code. Instead, the code
// being evaluated would be much more helpful.
sourceMaps: false,
},
},
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
// In production, we use MiniCSSExtractPlugin to extract that CSS
// to a file, but in development "style" loader enables hot editing
// of CSS.
// By default we support CSS Modules with the extension .module.css
{
test: cssRegex,
exclude: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
// using the extension .module.css
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true,
getLocalIdent: getCSSModuleLocalIdent,
}),
},
// Opt-in support for SASS (using .scss or .sass extensions).
// By default we support SASS Modules with the
// extensions .module.scss or .module.sass
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
},
'sass-loader'
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules, but using SASS
// using the extension .module.scss or .module.sass
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true,
getLocalIdent: getCSSModuleLocalIdent,
},
'sass-loader'
),
},
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise be processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
],
},
plugins: [
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
template: paths.appHtml,
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),
// Inlines the webpack runtime script. This script is too small to warrant
// a network request.
isEnvProduction &&
shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In production, it will be an empty string unless you specify "homepage"
// in `package.json`, in which case it will be the pathname of that URL.
// In development, this will be an empty string.
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
// This gives some necessary context to module not found errors, such as
// the requesting resource.
new ModuleNotFoundPlugin(paths.appPath),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
// It is absolutely essential that NODE_ENV is set to production
// during a production build.
// Otherwise React will be compiled in the very slow development mode.
new webpack.DefinePlugin(env.stringified),
// This is necessary to emit hot updates (currently CSS only):
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebook/create-react-app/issues/240
isEnvDevelopment && new CaseSensitivePathsPlugin(),
// If you require a missing module and then `npm install` it, you still have
// to restart the development server for Webpack to discover it. This plugin
// makes the discovery automatic so you don't have to restart.
// See https://github.com/facebook/create-react-app/issues/186
isEnvDevelopment &&
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
isEnvProduction &&
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
}),
// Generate a manifest file which contains a mapping of all asset filenames
// to their corresponding output file so that tools can pick it up without
// having to parse `index.html`.
new ManifestPlugin({
fileName: 'asset-manifest.json',
publicPath: publicPath,
generate: (seed, files) => {
const manifestFiles = files.reduce(function(manifest, file) {
manifest[file.name] = file.path;
return manifest;
}, seed);
return {
files: manifestFiles,
};
},
}),
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how Webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the Webpack build.
isEnvProduction &&
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true,
exclude: [/\.map$/, /asset-manifest\.json$/],
importWorkboxFrom: 'cdn',
navigateFallback: publicUrl + '/index.html',
navigateFallbackBlacklist: [
// Exclude URLs starting with /_, as they're likely an API call
new RegExp('^/_'),
// Exclude URLs containing a dot, as they're likely a resource in
// public/ and not a SPA route
new RegExp('/[^/]+\\.[^/]+$'),
],
}),
// TypeScript type checking
useTypeScript &&
new ForkTsCheckerWebpackPlugin({
typescript: resolve.sync('typescript', {
basedir: paths.appNodeModules,
}),
async: isEnvDevelopment,
useTypescriptIncrementalApi: true,
checkSyntacticErrors: true,
resolveModuleNameModule: process.versions.pnp
? `${__dirname}/pnpTs.js`
: undefined,
resolveTypeReferenceDirectiveModule: process.versions.pnp
? `${__dirname}/pnpTs.js`
: undefined,
tsconfig: paths.appTsConfig,
reportFiles: [
'**',
'!**/__tests__/**',
'!**/?(*.)(spec|test).*',
'!**/src/setupProxy.*',
'!**/src/setupTests.*',
],
watch: paths.appSrc,
silent: true,
// The formatter is invoked directly in WebpackDevServerUtils during development
formatter: isEnvProduction ? typescriptFormatter : undefined,
}),
].filter(Boolean),
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
module: 'empty',
dgram: 'empty',
dns: 'mock',
fs: 'empty',
http2: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
// Turn off performance processing because we utilize
// our own hints via the FileSizeReporter
performance: false,
};
};

@ -1,104 +0,0 @@
'use strict';
const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
const ignoredFiles = require('react-dev-utils/ignoredFiles');
const paths = require('./paths');
const fs = require('fs');
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const host = process.env.HOST || '0.0.0.0';
module.exports = function(proxy, allowedHost) {
return {
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote
// websites from potentially accessing local content through DNS rebinding:
// https://github.com/webpack/webpack-dev-server/issues/887
// https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
// However, it made several existing use cases such as development in cloud
// environment or subdomains in development significantly more complicated:
// https://github.com/facebook/create-react-app/issues/2271
// https://github.com/facebook/create-react-app/issues/2233
// While we're investigating better solutions, for now we will take a
// compromise. Since our WDS configuration only serves files in the `public`
// folder we won't consider accessing them a vulnerability. However, if you
// use the `proxy` feature, it gets more dangerous because it can expose
// remote code execution vulnerabilities in backends like Django and Rails.
// So we will disable the host check normally, but enable it if you have
// specified the `proxy` setting. Finally, we let you override it if you
// really know what you're doing with a special environment variable.
disableHostCheck:
!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
// Enable gzip compression of generated files.
compress: true,
// Silence WebpackDevServer's own logs since they're generally not useful.
// It will still show compile warnings and errors with this setting.
clientLogLevel: 'none',
// By default WebpackDevServer serves physical files from current directory
// in addition to all the virtual build products that it serves from memory.
// This is confusing because those files wont automatically be available in
// production build folder unless we copy them. However, copying the whole
// project directory is dangerous because we may expose sensitive files.
// Instead, we establish a convention that only files in `public` directory
// get served. Our build script will copy `public` into the `build` folder.
// In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
// Note that we only recommend to use `public` folder as an escape hatch
// for files like `favicon.ico`, `manifest.json`, and libraries that are
// for some reason broken when imported through Webpack. If you just want to
// use an image, put it in `src` and `import` it from JavaScript instead.
contentBase: paths.appPublic,
// By default files from `contentBase` will not trigger a page reload.
watchContentBase: true,
// Enable hot reloading server. It will provide /sockjs-node/ endpoint
// for the WebpackDevServer client so it can learn when the files were
// updated. The WebpackDevServer client is included as an entry point
// in the Webpack development configuration. Note that only changes
// to CSS are currently hot reloaded. JS changes will refresh the browser.
hot: true,
// It is important to tell WebpackDevServer to use the same "root" path
// as we specified in the config. In development, we always serve from /.
publicPath: '/',
// WebpackDevServer is noisy by default so we emit custom message instead
// by listening to the compiler events with `compiler.hooks[...].tap` calls above.
quiet: true,
// Reportedly, this avoids CPU overload on some systems.
// https://github.com/facebook/create-react-app/issues/293
// src/node_modules is not ignored to support absolute imports
// https://github.com/facebook/create-react-app/issues/1065
watchOptions: {
ignored: ignoredFiles(paths.appSrc),
},
// Enable HTTPS if the HTTPS environment variable is set to 'true'
https: protocol === 'https',
host,
overlay: false,
historyApiFallback: {
// Paths with dots should still use the history fallback.
// See https://github.com/facebook/create-react-app/issues/387.
disableDotRule: true,
},
public: allowedHost,
proxy,
before(app, server) {
if (fs.existsSync(paths.proxySetup)) {
// This registers user provided middleware for proxy reasons
require(paths.proxySetup)(app);
}
// This lets us fetch source contents from webpack for the error overlay
app.use(evalSourceMapMiddleware(server));
// This lets us open files from the runtime error overlay.
app.use(errorOverlayMiddleware());
// This service worker file is effectively a 'no-op' that will reset any
// previous service worker registered for the same host:port combination.
// We do this in development to avoid hitting the production cache if
// it used the same host and port.
// https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
app.use(noopServiceWorkerMiddleware());
},
};
};

@ -1,218 +0,0 @@
<!DOCTYPE html>
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<style type="text/css">
/* FONTS */
@media screen {
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 400;
src: local('Lato Regular'), local('Lato-Regular'), url(https://fonts.gstatic.com/s/lato/v11/qIIYRU-oROkIk8vfvxw6QvesZW2xOQ-xsNqO47m55DA.woff) format('woff');
}
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 700;
src: local('Lato Bold'), local('Lato-Bold'), url(https://fonts.gstatic.com/s/lato/v11/qdgUG4U09HnJwhYI-uK18wLUuEpTyoUstqEm5AMlJo4.woff) format('woff');
}
@font-face {
font-family: 'Lato';
font-style: italic;
font-weight: 400;
src: local('Lato Italic'), local('Lato-Italic'), url(https://fonts.gstatic.com/s/lato/v11/RYyZNoeFgb0l7W3Vu1aSWOvvDin1pK8aKteLpeZ5c0A.woff) format('woff');
}
@font-face {
font-family: 'Lato';
font-style: italic;
font-weight: 700;
src: local('Lato Bold Italic'), local('Lato-BoldItalic'), url(https://fonts.gstatic.com/s/lato/v11/HkF_qI1x_noxlxhrhMQYELO3LdcAZYWl9Si6vvxL-qU.woff) format('woff');
}
}
/* CLIENT-SPECIFIC STYLES */
body, table, th, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
img { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none;}
/* RESET STYLES */
table { border-collapse: collapse !important; padding: 0 !important;}
body { height: 100% !important; margin: 0 !important; padding: 0 !important; width: 100% !important; }
/* iOS BLUE LINKS */
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* MOBILE STYLES */
@media screen and (max-width:600px){
h1 {
font-size: 32px !important;
line-height: 32px !important;
}
}
/* ANDROID CENTER FIX */
div[style*="margin: 16px 0;"] { margin: 0 !important; }
</style>
</head>
<body style="background-color: #f4f4f4; margin: 0 !important; padding: 0 !important;">
<!-- HIDDEN PREHEADER TEXT -->
<div style="display: none; font-size: 1px; color: #fefefe; line-height: 1px; font-family: 'Lato', Helvetica, Arial, sans-serif; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden;">
{{#i18n 'email.happy' }}We are happy to send you this email! You will be able to vote using majority judgment.{{/i18n}}
</div>
<table border="0" style="margin: 0px auto 0px auto; width: 100%;" aria-describedby="Email">
<!-- LOGO -->
<tr>
<th scope="col" style="background-color:#efefff ;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="Logo picture">
<tr>
<th scope="col" style="vertical-align: top; padding: 40px 10px 40px 10px;">
<a href="https://mieuxvoter.fr/" target="_blank" rel="noopener noreferrer">
<img alt="Logo" src="https://mieuxvoter.fr/wp-content/uploads/2019/10/mieuxvoter_logo.png" width="40" height="40" style="display: block; margin: 0px auto 0px auto; width: 50%; max-width: 250px; min-width: 40px; height: auto; font-family: 'Lato', Helvetica, Arial, sans-serif; color: #ffffff; font-size: 18px;" border="0">
</a>
</th>
</tr>
</table>
</th>
</tr>
<!-- TITLE -->
<tr>
<th scope="col" style="background-color: #efefff; padding: 0px 10px 0px 10px;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="email title">
<tr>
<th scope="col" style="vertical-align: top; background-color: #ffffff; padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">{{#i18n 'email.hello'}}Hi, there! 🙂{{/i18n}}</h1>
</th>
</tr>
</table>
</th>
</tr>
<!-- BLOCKS -->
<tr>
<th scope="col" style="background-color: #2a43a0; padding: 0px 10px 0px 10px;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="email body">
<!-- BLOCK SUBTITLE-->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 40px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">
{{#i18n 'email.happy'}}We are happy to send you this email! You will be able to vote using majority judgment.{{/i18n}}
</p>
</th>
</tr>
<!-- BLOCK EXPLANATION-->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 40px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">
{{#i18n 'email.why'}}This email was sent to you because your email address was entered to participate in the vote on the subject:{{/i18n}}
&nbsp;
<strong>{{title}}</strong>
</p>
</th>
</tr>
<!-- BULLETPROOF BUTTON BLUE-->
<tr>
<th scope="col" style="background-color: #ffffff;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%;" aria-describedby="Blue bulletproof button">
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 60px 30px;">
<table border="0" style="margin: 0px auto 0px auto; border-collapse: collapse;" aria-describedby="invitation url">
<tr>
<th scope="col" style="border-radius: 3px; background-color: #2a43a0;">
<a href="%recipient.urlVote%" target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #2a43a0; display: inline-block;">
{{#i18n 'common.vote' }}Vote!{{/i18n}}</a></th>
</tr>
</table>
</th>
</tr>
</table>
</th>
</tr>
<!-- BLOCK DOES NOT WORK -->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 40px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">
{{#i18n 'email.copyLink' }}If that doesn't work, copy and paste the following link into your browser:{{/i18n}}
&nbsp;
<a target="_blank" style="color: #2a43a0;">%recipient.urlVote%</a>
</p>
</th>
</tr>
<!-- BLOCK TEXT RESULT -->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 20px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">
{{#i18n 'email.linkResult' }}The results will be available with the following link when the vote is finished:{{/i18n}}
&nbsp;
<a target="_blank" style="color: #2a43a0;">%recipient.urlResult%</a>
</p>
</th>
</tr>
<!-- BLOCK THANKS -->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 0px 30px 40px 30px; border-radius: 0px 0px 4px 4px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">{{#i18n 'email.bye'}}Good vote{{/i18n}},<br>{{#i18n 'common.mieuxvoter'}}Mieux Voter{{/i18n}}</p>
</th>
</tr>
</table>
</th>
</tr>
<!-- SUPPORT CALLOUT -->
<tr>
<th scope="col" style="background-color: #f4f4f4; padding: 30px 10px 0px 10px;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="support callout">
<!-- HEADLINE -->
<tr>
<th scope="col" style="background-color: #7d8ecf; padding: 30px 30px 30px 30px; border-radius: 4px 4px 4px 4px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0;"><strong>
<a href="https://mieuxvoter.fr/index.php/decouvrir/" target="_blank" style="color: #FFFFFF;" rel="noopener noreferrer">
{{#i18n 'email.aboutjm'}}Need any further information?{{/i18n}}
</a></strong>
</p>
<p style="margin: 0;"> <strong>
<a href="https://mieuxvoter.fr/index.php/decouvrir/" target="_blank" style="color: #111111;" rel="noopener noreferrer">
{{#i18n 'common.helpus'}}Do you want to help us?{{/i18n}}
</a></strong>
</p>
</th>
</tr>
</table>
</th>
</tr>
<!-- FOOTER -->
<tr>
<th scope="col" style="background-color: #f4f4f4; padding: 0px 10px 0px 10px;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="footer informations">
<!-- EXPLAIN WHY -->
</br>
<tr>
<th scope="col" style="background-color: #f4f4f4; padding: 0px 30px 30px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;" >
<p style="margin: 0;">
{{#i18n email.why }}You received this email because someone invited you to vote.{{/i18n}}
</p>
</th>
</tr>
<!-- ADDRESS -->
<tr>
<th scope="col" style="background-color: #f4f4f4; padding: 0px 30px 30px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;" >
<p style="margin: 0;">{{#i18n mieuxvoter }}Mieux Voter{{/i18n}} - <a "mailto:app@mieuxvoter.fr">app@mieuxvoter.fr</a></p>
</th>
</tr>
</table>
</th>
</tr>
</table>
</body>
</html>

@ -1,19 +0,0 @@
{{i18n 'email.hello'}}Hi there! 🙂{{i18n}}
{{i18n 'email.happy'}}We are happy to send you this email! You will be able to vote using majority judgment.{{i18n}}
{{i18n 'email.why'}}This email was sent to you because your email was filled out to participate in the vote on the subject:{{i18n}}
{{ title }}
{{i18n 'email.linkVote' }}The link for the vote is as follows:{{i18n}}
%recipient.urlVote%
{{i18n 'email.linkResult' }}The link that will give you the results when they are available is as follows:{{i18n}}
%recipient.urlResult%
{{i18n 'email.bye'}}Good vote{{i18n}}
{{i18n 'common.mieuxvoter'}}Mieux Voter{{i18n}}

@ -1,203 +0,0 @@
<!DOCTYPE html>
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<style type="text/css">
/* FONTS */
@media screen {
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 400;
src: local('Lato Regular'), local('Lato-Regular'), url(https://fonts.gstatic.com/s/lato/v11/qIIYRU-oROkIk8vfvxw6QvesZW2xOQ-xsNqO47m55DA.woff) format('woff');
}
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 700;
src: local('Lato Bold'), local('Lato-Bold'), url(https://fonts.gstatic.com/s/lato/v11/qdgUG4U09HnJwhYI-uK18wLUuEpTyoUstqEm5AMlJo4.woff) format('woff');
}
@font-face {
font-family: 'Lato';
font-style: italic;
font-weight: 400;
src: local('Lato Italic'), local('Lato-Italic'), url(https://fonts.gstatic.com/s/lato/v11/RYyZNoeFgb0l7W3Vu1aSWOvvDin1pK8aKteLpeZ5c0A.woff) format('woff');
}
@font-face {
font-family: 'Lato';
font-style: italic;
font-weight: 700;
src: local('Lato Bold Italic'), local('Lato-BoldItalic'), url(https://fonts.gstatic.com/s/lato/v11/HkF_qI1x_noxlxhrhMQYELO3LdcAZYWl9Si6vvxL-qU.woff) format('woff');
}
}
/* CLIENT-SPECIFIC STYLES */
body, table, th, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
img { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none;}
/* RESET STYLES */
table { border-collapse: collapse !important; padding: 0 !important;}
body { height: 100% !important; margin: 0 !important; padding: 0 !important; width: 100% !important; }
/* iOS BLUE LINKS */
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* MOBILE STYLES */
@media screen and (max-width:600px){
h1 {
font-size: 32px !important;
line-height: 32px !important;
}
}
/* ANDROID CENTER FIX */
div[style*="margin: 16px 0;"] { margin: 0 !important; }
</style>
</head>
<body style="background-color: #f4f4f4; margin: 0 !important; padding: 0 !important;">
<!-- HIDDEN PREHEADER TEXT -->
<div style="display: none; font-size: 1px; color: #fefefe; line-height: 1px; font-family: 'Lato', Helvetica, Arial, sans-serif; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden;">Nous sommes très heureux de vous partager ce lien de vote ! Vous allez pouvoir voter avec le jugement majoritaire.</div>
<table border="0" style="margin: 0px auto 0px auto; width: 100%;" aria-describedby="Email">
<!-- LOGO -->
<tr>
<th scope="col" style="background-color:#efefff ;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="Logo picture">
<tr>
<th scope="col" style="vertical-align: top; padding: 40px 10px 40px 10px;">
<a href="https://mieuxvoter.fr/" target="_blank" rel="noopener noreferrer">
<img alt="Logo" src="https://mieuxvoter.fr/wp-content/uploads/2019/10/mieuxvoter_logo.png" width="40" height="40" style="display: block; margin: 0px auto 0px auto; width: 50%; max-width: 250px; min-width: 40px; height: auto; font-family: 'Lato', Helvetica, Arial, sans-serif; color: #ffffff; font-size: 18px;" border="0">
</a>
</th>
</tr>
</table>
</th>
</tr>
<!-- TITLE -->
<tr>
<th scope="col" style="background-color: #efefff; padding: 0px 10px 0px 10px;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="email title">
<tr>
<th scope="col" style="vertical-align: top; background-color: #ffffff; padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">Bonjour ! 🙂</h1>
</th>
</tr>
</table>
</th>
</tr>
<!-- BLOCKS -->
<tr>
<th scope="col" style="background-color: #2a43a0; padding: 0px 10px 0px 10px;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="email body">
<!-- BLOCK SUBTITLE-->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 40px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">Nous sommes très heureux de vous partager ce lien de vote ! Vous allez pouvoir voter avec le jugement majoritaire.</p>
</th>
</tr>
<!-- BLOCK EXPLANATION-->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 40px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">Vous avez été invité·e à participer à l'élection suivante : </p>
</th>
</tr>
<!-- BULLETPROOF BUTTON BLUE-->
<tr>
<th scope="col" style="background-color: #ffffff;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%;" aria-describedby="Blue bulletproof button">
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 60px 30px;">
<table border="0" style="margin: 0px auto 0px auto; border-collapse: collapse;" aria-describedby="invitation url">
<tr>
<th scope="col" style="border-radius: 3px; background-color: #2a43a0;">
<a href="%recipient.urlVote%" target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #2a43a0; display: inline-block;">Voter !</a></th>
</tr>
</table>
</th>
</tr>
</table>
</th>
</tr>
<!-- BLOCK DOES NOT WORK -->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 40px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">
Si le lien ne fonctionne pas, vous pouvez le copier et le coller dans la barre de navigation de votre navigateur.
&nbsp;
<a target="_blank" style="color: #2a43a0;">%recipient.urlVote%</a>
</p>
</th>
</tr>
<!-- BLOCK TEXT RESULT -->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 20px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">
A la fin de l'élection, vous pourrez accéder aux résultats en cliquant sur ce lien :
&nbsp;
<a target="_blank" style="color: #2a43a0;">%recipient.urlResult%</a>
</p>
</th>
</tr>
<!-- BLOCK THANKS -->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 0px 30px 40px 30px; border-radius: 0px 0px 4px 4px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">Bon vote,<br>Mieux Voter</p>
</th>
</tr>
</table>
</th>
</tr>
<!-- SUPPORT CALLOUT -->
<tr>
<th scope="col" style="background-color: #f4f4f4; padding: 30px 10px 0px 10px;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="support callout">
<!-- HEADLINE -->
<tr>
<th scope="col" style="background-color: #7d8ecf; padding: 30px 30px 30px 30px; border-radius: 4px 4px 4px 4px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0;"><strong>
<a href="https://mieuxvoter.fr/index.php/decouvrir/" target="_blank" style="color: #FFFFFF;" rel="noopener noreferrer">Besoin de plus d'information</a></strong>
</p>
<p style="margin: 0;"> <strong>
<a href="https://mieuxvoter.fr/index.php/decouvrir/" target="_blank" style="color: #111111;" rel="noopener noreferrer">Vous souhaitez nous aider ?</a></strong>
</p>
</th>
</tr>
</table>
</th>
</tr>
<!-- FOOTER -->
<tr>
<th scope="col" style="background-color: #f4f4f4; padding: 0px 10px 0px 10px;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="footer informations">
<!-- EXPLAIN WHY -->
</br>
<tr>
<th scope="col" style="background-color: #f4f4f4; padding: 0px 30px 30px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;" >
<p style="margin: 0;">Vous avez été invité·e à participer à l'élection suivante</p>
</th>
</tr>
<!-- ADDRESS -->
<tr>
<th scope="col" style="background-color: #f4f4f4; padding: 0px 30px 30px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;" >
<p style="margin: 0;">Mieux Voter - <a "mailto:app@mieuxvoter.fr">app@mieuxvoter.fr</a></p>
</th>
</tr>
</table>
</th>
</tr>
</table>
</body>
</html>

@ -1,17 +0,0 @@
Bonjour ! 🙂
Vous avez été invité·e à participer à l'élection suivante :
{{ title }}
Le lien pour voter est le suivant :
%recipient.urlVote%
A la fin de l'élection, vous pourrez accéder aux résultats en cliquant sur ce lien :
%recipient.urlResult%
Bon vote ! 🤗
Mieux Voter

@ -1,218 +0,0 @@
<!DOCTYPE html>
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<style type="text/css">
/* FONTS */
@media screen {
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 400;
src: local('Lato Regular'), local('Lato-Regular'), url(https://fonts.gstatic.com/s/lato/v11/qIIYRU-oROkIk8vfvxw6QvesZW2xOQ-xsNqO47m55DA.woff) format('woff');
}
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 700;
src: local('Lato Bold'), local('Lato-Bold'), url(https://fonts.gstatic.com/s/lato/v11/qdgUG4U09HnJwhYI-uK18wLUuEpTyoUstqEm5AMlJo4.woff) format('woff');
}
@font-face {
font-family: 'Lato';
font-style: italic;
font-weight: 400;
src: local('Lato Italic'), local('Lato-Italic'), url(https://fonts.gstatic.com/s/lato/v11/RYyZNoeFgb0l7W3Vu1aSWOvvDin1pK8aKteLpeZ5c0A.woff) format('woff');
}
@font-face {
font-family: 'Lato';
font-style: italic;
font-weight: 700;
src: local('Lato Bold Italic'), local('Lato-BoldItalic'), url(https://fonts.gstatic.com/s/lato/v11/HkF_qI1x_noxlxhrhMQYELO3LdcAZYWl9Si6vvxL-qU.woff) format('woff');
}
}
/* CLIENT-SPECIFIC STYLES */
body, table, th, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
img { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none;}
/* RESET STYLES */
table { border-collapse: collapse !important; padding: 0 !important;}
body { height: 100% !important; margin: 0 !important; padding: 0 !important; width: 100% !important; }
/* iOS BLUE LINKS */
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* MOBILE STYLES */
@media screen and (max-width:600px){
h1 {
font-size: 32px !important;
line-height: 32px !important;
}
}
/* ANDROID CENTER FIX */
div[style*="margin: 16px 0;"] { margin: 0 !important; }
</style>
</head>
<body style="background-color: #f4f4f4; margin: 0 !important; padding: 0 !important;">
<!-- HIDDEN PREHEADER TEXT -->
<div style="display: none; font-size: 1px; color: #fefefe; line-height: 1px; font-family: 'Lato', Helvetica, Arial, sans-serif; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden;">
{{#i18n 'email.happy' }}We are happy to send you this email! You will be able to vote using majority judgment.{{/i18n}}
</div>
<table border="0" style="margin: 0px auto 0px auto; width: 100%;" aria-describedby="Email">
<!-- LOGO -->
<tr>
<th scope="col" style="background-color:#efefff ;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="Logo picture">
<tr>
<th scope="col" style="vertical-align: top; padding: 40px 10px 40px 10px;">
<a href="https://mieuxvoter.fr/" target="_blank" rel="noopener noreferrer">
<img alt="Logo" src="https://mieuxvoter.fr/wp-content/uploads/2019/10/mieuxvoter_logo.png" width="40" height="40" style="display: block; margin: 0px auto 0px auto; width: 50%; max-width: 250px; min-width: 40px; height: auto; font-family: 'Lato', Helvetica, Arial, sans-serif; color: #ffffff; font-size: 18px;" border="0">
</a>
</th>
</tr>
</table>
</th>
</tr>
<!-- TITLE -->
<tr>
<th scope="col" style="background-color: #efefff; padding: 0px 10px 0px 10px;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="email title">
<tr>
<th scope="col" style="vertical-align: top; background-color: #ffffff; padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">{{#i18n 'email.hello'}}Hi, there! 🙂{{/i18n}}</h1>
</th>
</tr>
</table>
</th>
</tr>
<!-- BLOCKS -->
<tr>
<th scope="col" style="background-color: #2a43a0; padding: 0px 10px 0px 10px;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="email body">
<!-- BLOCK SUBTITLE-->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 40px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">
{{#i18n 'email.happy'}}We are happy to send you this email! You will be able to vote using majority judgment.{{/i18n}}
</p>
</th>
</tr>
<!-- BLOCK EXPLANATION-->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 40px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">
{{#i18n 'email.why'}}This email was sent to you because your email address was entered to participate in the vote on the subject:{{/i18n}}
&nbsp;
<strong>{{title}}</strong>
</p>
</th>
</tr>
<!-- BULLETPROOF BUTTON BLUE-->
<tr>
<th scope="col" style="background-color: #ffffff;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%;" aria-describedby="Blue bulletproof button">
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 60px 30px;">
<table border="0" style="margin: 0px auto 0px auto; border-collapse: collapse;" aria-describedby="invitation url">
<tr>
<th scope="col" style="border-radius: 3px; background-color: #2a43a0;">
<a href="%recipient.urlVote%" target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #2a43a0; display: inline-block;">
{{#i18n 'common.vote' }}Vote!{{/i18n}}</a></th>
</tr>
</table>
</th>
</tr>
</table>
</th>
</tr>
<!-- BLOCK DOES NOT WORK -->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 40px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">
{{#i18n 'email.copyLink' }}If that doesn't work, copy and paste the following link into your browser:{{/i18n}}
&nbsp;
<a target="_blank" style="color: #2a43a0;">%recipient.urlVote%</a>
</p>
</th>
</tr>
<!-- BLOCK TEXT RESULT -->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 20px 30px 20px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">
{{#i18n 'email.linkResult' }}The results will be available with the following link when the vote is finished:{{/i18n}}
&nbsp;
<a target="_blank" style="color: #2a43a0;">%recipient.urlResult%</a>
</p>
</th>
</tr>
<!-- BLOCK THANKS -->
<tr>
<th scope="col" style="background-color: #ffffff; padding: 0px 30px 40px 30px; border-radius: 0px 0px 4px 4px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0; text-align: left;">{{#i18n 'email.bye'}}Good vote{{/i18n}},<br>{{#i18n 'common.mieuxvoter'}}Mieux Voter{{/i18n}}</p>
</th>
</tr>
</table>
</th>
</tr>
<!-- SUPPORT CALLOUT -->
<tr>
<th scope="col" style="background-color: #f4f4f4; padding: 30px 10px 0px 10px;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="support callout">
<!-- HEADLINE -->
<tr>
<th scope="col" style="background-color: #7d8ecf; padding: 30px 30px 30px 30px; border-radius: 4px 4px 4px 4px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0;"><strong>
<a href="https://mieuxvoter.fr/index.php/decouvrir/" target="_blank" style="color: #FFFFFF;" rel="noopener noreferrer">
{{#i18n 'email.aboutjm'}}Need any further information?{{/i18n}}
</a></strong>
</p>
<p style="margin: 0;"> <strong>
<a href="https://mieuxvoter.fr/index.php/decouvrir/" target="_blank" style="color: #111111;" rel="noopener noreferrer">
{{#i18n 'common.helpus'}}Do you want to help us?{{/i18n}}
</a></strong>
</p>
</th>
</tr>
</table>
</th>
</tr>
<!-- FOOTER -->
<tr>
<th scope="col" style="background-color: #f4f4f4; padding: 0px 10px 0px 10px;">
<table border="0" style="margin: 0px auto 0px auto; width: 100%; max-width: 600px;" aria-describedby="footer informations">
<!-- EXPLAIN WHY -->
</br>
<tr>
<th scope="col" style="background-color: #f4f4f4; padding: 0px 30px 30px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;" >
<p style="margin: 0;">
{{#i18n email.why }}You received this email because someone invited you to vote.{{/i18n}}
</p>
</th>
</tr>
<!-- ADDRESS -->
<tr>
<th scope="col" style="background-color: #f4f4f4; padding: 0px 30px 30px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;" >
<p style="margin: 0;">{{#i18n mieuxvoter }}Mieux Voter{{/i18n}} - <a "mailto:app@mieuxvoter.fr">app@mieuxvoter.fr</a></p>
</th>
</tr>
</table>
</th>
</tr>
</table>
</body>
</html>

@ -1,19 +0,0 @@
{{#i18n 'email.hello'}}Hi there! 🙂{{/i18n}}
{{#i18n 'email.happy'}}We are happy to send you this email! You will be able to vote using majority judgment.{{/i18n}}
{{#i18n 'email.why'}}This email was sent to you because your email was filled out to participate in the vote on the subject:{{/i18n}}
{{ title }}
{{#i18n 'email.linkVote' }}The link for the vote is as follows:{{/i18n}}
%recipient.urlVote%
{{#i18n 'email.linkResult' }}The link that will give you the results when they are available is as follows:{{/i18n}}
%recipient.urlResult%
{{#i18n 'email.bye'}}Good vote{{/i18n}}
{{#i18n 'common.mieuxvoter'}}Mieux Voter{{/i18n}}

@ -1,150 +0,0 @@
const fs = require("fs");
const Mailgun = require("mailgun.js");
const formData = require("form-data");
const dotenv = require("dotenv");
const i18next = require("i18next");
const Backend = require("i18next-chained-backend");
const FSBackend = require("i18next-fs-backend");
const HttpApi = require("i18next-http-backend");
const Handlebars = require("handlebars");
dotenv.config();
const {
MAILGUN_API_KEY,
MAILGUN_DOMAIN,
MAILGUN_URL,
FROM_EMAIL_ADDRESS,
CONTACT_TO_EMAIL_ADDRESS,
} = process.env;
const mailgun = new Mailgun(formData);
const mg = mailgun.client({
username: "api",
key: MAILGUN_API_KEY,
url: "https://api.eu.mailgun.net",
});
const success = {
statusCode: 200,
body: "Your message was sent successfully! We'll be in touch.",
};
const err = {
statusCode: 422,
body: "Can't send message",
};
// setup i18n
// i18next.use(Backend).init({
// lng: "fr",
// ns: ["emailInvite", "common"],
// defaultNS: "emailInvite",
// fallbackNS: "common",
// debug: false,
// fallbackLng: ["fr"],
// backend: {
// backends: [FSBackend, HttpApi],
// backendOptions: [{ loadPath: "/public/locales/{{lng}}/{{ns}}.json" }, {}],
// },
// });
// setup the template engine
// See https://github.com/UUDigitalHumanitieslab/handlebars-i18next
// function extend(target, ...sources) {
// sources.forEach((source) => {
// if (source)
// for (let key in source) {
// target[key] = source[key];
// }
// });
// return target;
// }
// Handlebars.registerHelper("i18n", function (key, { hash, data, fn }) {
// let parsed = {};
// const jsonKeys = [
// "lngs",
// "fallbackLng",
// "ns",
// "postProcess",
// "interpolation",
// ];
// jsonKeys.forEach((key) => {
// if (hash[key]) {
// parsed[key] = JSON.parse(hash[key]);
// delete hash[key];
// }
// });
// let options = extend({}, data.root.i18next, hash, parsed, {
// returnObjects: false,
// });
// let replace = (options.replace = extend({}, this, options.replace, hash));
// delete replace.i18next; // may creep in if this === data.root
// if (fn) options.defaultValue = fn(replace);
// return new Handlebars.SafeString(i18next.t(key, options));
// });
// const txtStr = fs.readFileSync(__dirname + "/invite.txt").toString();
const txtStr = {
en: fs.readFileSync(__dirname + "/invite-en.txt").toString(),
fr: fs.readFileSync(__dirname + "/invite-fr.txt").toString(),
};
const txtTemplate = {
en: Handlebars.compile(txtStr.en),
fr: Handlebars.compile(txtStr.fr),
};
const htmlStr = {
en: fs.readFileSync(__dirname + "/invite-en.html").toString(),
fr: fs.readFileSync(__dirname + "/invite-fr.html").toString(),
};
const htmlTemplate = {
en: Handlebars.compile(htmlStr.en),
fr: Handlebars.compile(htmlStr.fr),
};
const test = Handlebars.compile("test");
const sendMail = async (event) => {
if (event.httpMethod !== "POST") {
return {
statusCode: 405,
body: "Method Not Allowed",
headers: { Allow: "POST" },
};
}
const data = JSON.parse(event.body);
if (!data.recipientVariables || !data.title || !data.locale) {
return {
statusCode: 422,
body: "Recipient variables and title are required.",
};
}
// i18next.changeLanguage(data.locale);
const templateData = {
title: data.title,
};
const mailgunData = {
// from: `${i18next.t("Mieux Voter")} <mailgun@mg.app.mieuxvoter.fr>`,
from: '"Mieux Voter" <postmaster@mg.app.mieuxvoter.fr>',
to: Object.keys(data.recipientVariables),
text: txtTemplate.fr(templateData),
html: htmlTemplate.fr(templateData),
subject: data.title,
"h:Reply-To": "app@mieuxvoter.fr",
"recipient-variables": JSON.stringify(data.recipientVariables),
};
const res = mg.messages
.create("mg.app.mieuxvoter.fr", mailgunData)
.then((msg) => {
return success;
}) // logs response data
.catch((err) => {
console.log(err);
return success;
}); // logs any error
return res;
};
exports.handler = sendMail;

@ -1,82 +0,0 @@
const fs = require("fs");
const chalk = require("chalk");
module.exports = {
input: [
"app/**/*.{js,jsx}",
// Use ! to filter out files or directories
"!app/**/*.spec.{js,jsx}",
"!app/i18n/**",
"!**/node_modules/**"
],
output: ".",
options: {
debug: true,
func: {
list: ["i18next.t", "i18n.t", "t"],
extensions: [".js", ".jsx"]
},
trans: {
component: "Trans",
i18nKey: "i18nKey",
defaultsKey: "defaults",
extensions: [".js", ".jsx"],
fallbackKey: function(ns, value) {
return value;
},
acorn: {
ecmaVersion: 10, // defaults to 10
sourceType: "module" // defaults to 'module'
// Check out https://github.com/acornjs/acorn/tree/master/acorn#interface for additional options
}
},
lngs: ["en", "fr", "es", "de", "ru"],
ns: ["resource", "common"],
defaultLng: "en",
defaultNs: "resource",
defaultValue: "__STRING_NOT_TRANSLATED__",
resource: {
loadPath: "./public/locale/i18n/{{lng}}/{{ns}}.json",
savePath: "./public/locale/i18n/{{lng}}/{{ns}}.json",
jsonIndent: 2,
lineEnding: "\n"
},
nsSeparator: false, // namespace separator
keySeparator: false, // key separator
interpolation: {
prefix: "{{",
suffix: "}}"
}
},
transform: function customTransform(file, enc, done) {
"use strict";
const parser = this.parser;
const content = fs.readFileSync(file.path, enc);
let count = 0;
parser.parseFuncFromString(
content,
{ list: ["i18next._", "i18next.__"] },
(key, options) => {
parser.set(
key,
Object.assign({}, options, {
nsSeparator: false,
keySeparator: false
})
);
++count;
}
);
if (count > 0) {
/* console.log(
`i18next-scanner: count=${chalk.cyan(count)}, file=${chalk.yellow(
JSON.stringify(file.relative)
)}`
);*/
}
done();
}
};

@ -1,10 +0,0 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@components/*": ["components/*"],
"@styles/*": ["styles/*"],
"@services/*": ["services/*"]
}
}
}

@ -1,7 +0,0 @@
[build]
command = "npm run build"
publish = "out"
functions = "functions"
[dev]
command = "npm run dev"

@ -1,9 +0,0 @@
module.exports = {
i18n: {
defaultLocale: "fr",
locales: ["en", "fr", "de", "es", "ru"],
ns: ["resource", "common", "error"],
defaultNS: "resource",
fallbackNS: ["common", "error"],
},
};

@ -1,45 +0,0 @@
const { i18n } = require("./next-i18next.config");
module.exports = {
i18n,
// See https://github.com/netlify/netlify-plugin-nextjs/issues/223
unstableNetlifyFunctionsSupport: {
"pages/index.jsx": {
includeDirs: ["public"],
},
"pages/faq.jsx": {
includeDirs: ["public"],
},
"pages/legal-notices.jsx": {
includeDirs: ["public"],
},
"pages/new/confirm/[pid].jsx": {
includeDirs: ["public"],
},
"pages/new.jsx": {
includeDirs: ["public"],
},
"pages/result/[pid]/[[...tid]].jsx": {
includeDirs: ["public"],
},
"pages/vote/[pid]/[[...tid]].jsx": {
includeDirs: ["public"],
},
"pages/vote/[pid]/confirm.jsx": {
includeDirs: ["public"],
},
"pages/privacy-policy.jsx": {
includeDirs: ["public"],
},
},
pageExtensions: ["mdx", "jsx", "js", "ts", "tsx"],
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ["@svgr/webpack"],
});
return config;
},
target: "experimental-serverless-trace",
};

4779
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,46 +1,46 @@
{
"name": "mv-front-react",
"version": "1.1.0",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"export": "next export"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.3",
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-brands-svg-icons": "^5.15.3",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/react-fontawesome": "^0.1.14",
"@svgr/webpack": "^5.5.0",
"array-move": "^3.0.1",
"bootstrap": "^4.6.0",
"bootstrap-scss": "^4.6.0",
"domexception": "^2.0.1",
"dotenv": "^8.6.0",
"form-data": "^4.0.0",
"handlebars": "^4.7.7",
"handlebars-i18next": "^1.0.1",
"i18next": "^20.2.2",
"i18next-chained-backend": "^2.1.0",
"i18next-fs-backend": "^1.1.1",
"i18next-http-backend": "^1.2.4",
"i18next-localstorage-backend": "^3.1.2",
"i18next-text": "^0.5.6",
"mailgun.js": "^3.3.2",
"next": "^10.2.0",
"next-i18next": "^8.2.0",
"query-string": "^7.0.0",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.4.0",
"@types/node": "^16.11.22",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.11",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-flags-select": "^2.1.2",
"react-i18next": "^11.8.15",
"react-multi-email": "^0.5.3",
"react-sortable-hoc": "^2.0.0",
"react-toastify": "^7.0.4",
"reactstrap": "^8.9.0",
"sass": "^1.32.13"
"react-scripts": "5.0.0",
"typescript": "^4.5.5",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"deploy": "gh-pages -d build"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"gh-pages": "^3.2.3"
}
}

@ -1,34 +0,0 @@
import Head from 'next/head'
import '@styles/globals.css'
import '@styles/footer.css'
import '@styles/loader.css'
import "@styles/scss/config.scss";
import '@fortawesome/fontawesome-svg-core/styles.css'
import {appWithTranslation} from 'next-i18next'
import {AppProvider} from '@services/context.js'
import Header from '@components/layouts/Header'
import Footer from '@components/layouts/Footer'
function Application({Component, pageProps}) {
const origin = typeof window !== 'undefined' && window.location.origin ? window.location.origin : 'http://localhost';
return (<AppProvider>
<Head>
<link rel="icon" key="favicon" href="/favicon.ico" />
<meta property="og:url" content={origin} key="og:url" />
<meta property="og:type" content="website" key="og:type" />
<meta
property="og:image"
content="https://app.mieuxvoter.fr/app-mieux-voter.png"
key="og:image"
/>
</Head>
<Header />
<main className="d-flex flex-column justify-content-center">
<Component {...pageProps} />
</main>
<Footer />
</AppProvider>);
}
export default appWithTranslation(Application)

@ -1,262 +0,0 @@
import Link from "next/link";
import { Container, Row, Col } from "reactstrap";
import { useTranslation } from "next-i18next";
import Paypal from "@components/banner/Paypal";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import config from "../next-i18next.config.js";
export const getStaticProps = async ({ locale }) => ({
props: {
...(await serverSideTranslations(locale, [], config)),
},
});
const FAQ = (props) => {
const { t } = useTranslation();
return (
<Container>
<Row>
<Link href="/" className="d-block ml-auto mr-auto mb-4">
<img src="/logos/logo-line-white.svg" alt="logo" height="128" />
</Link>
</Row>
<Row className="mt-4">
<Col className="text-center">
<h1>{t("FAQ")}</h1>
</Col>
</Row>
<Row className="mt-4">
<Col>
<h4 className="bold mt-5">Quest-ce que le Jugement Majoritaire ?</h4>
<p>
Un principe simple et intuitif, qui change tout : lélecteur vote en
donnant son avis sur toutes les candidatures présentées, leur
attribuant la mention de son choix (par exemple. Très bien, Bien,
Assez bien, Passable, Insuffisant, À Rejeter). La candidature
retenue est celle jugée la plus méritante par la majorité de
lélectorat (celui qui obtient la meilleure mention « majoritaire
»).
</p>
<div style={{ maxWidth: "445px" }}>
<video width="100%" height="250" controls="controls">
<source
src="/video/Le_Jugement_Majoritaire_en_1_minute.mp4"
type="video/mp4"
/>
<source
src="/video/Le_Jugement_Majoritaire_en_1_minute.webm"
type="video/webm"
/>
<source
src="/video/Le_Jugement_Majoritaire_en_1_minute.3gpp"
type="video/3gpp"
/>
</video>
</div>
<h4 className="bold mt-5">D vient le Jugement Majoritaire ?</h4>
<p>
Le jugement majoritaire est un mode de scrutin inventé par deux
chercheurs Français du Centre National de la Recherche Scientifique
(CNRS) en 2011, <u>Michel Balinski</u> et <u>Rida Laraki</u>.
</p>
<h4 className="bold mt-5">
Quels sont les avantages du Jugement Majoritaire ?
</h4>
<p>
Une mesure précise de lopinion des participants au vote, à même
déclairer la décision collective. En demandant aux électeurs leur
opinion sur chaque option soumise au vote, on bénéficie de beaucoup
plus dinformations que dans le cadre du scrutin uninominal qui,
résumant lopinion des électeurs à un choix, ignore lessentiel de
linformation quant à ce quils pensent. En agrégeant un grand
nombre dinformations, le Jugement Majoritaire ne produit pas «
juste » un gagnant qui obtiendrait la majorité des voix. Il mesure
précisément le crédit porté à chacune des options et permet
daffiner autant que de pacifier la prise de décision.
</p>
<h4 className="bold mt-5">
Quand et comment utiliser le Jugement Majoritaire ?
</h4>
<p>
Le Jugement majoritaire sapplique à tout type de votation
collective, quil sagisse délire un candidat, de retenir une ou
plusieurs idées lors dun atelier collaboratif, de choisir entre
plusieurs projets, de classer les vins, etc. Il peut être utilisé à
toutes les échelles (locale, nationale, internationale) et dans tous
les milieux (écoles, entreprises, associations, coopératives,
collectivités publiques).
</p>
<h4 className="bold mt-5">Qui peut utiliser cette application ?</h4>
<p>
Cette application de Jugement Majoritaire est ouverte à toute
personne désireuse de prendre une décision collective, entre amis,
entre collègues, entre membres dun groupe. Elle est libre daccès
et gratuite. Notre ambition est de vous proposer la meilleure
expérience de prise de décision collective et démocratique.
</p>
<h4 className="bold mt-5">
Comment organiser une élection avec plusieurs milliers de votants ?
</h4>
<p>
Cette application ne convient pas pour les votes à plus de 1000
votants. Si cest votre cas, nous vous invitons à nous contacter par
email à ladresse{" "}
<a href="mailto:contact@mieuxvoter.fr" className="text-light">
contact@mieuxvoter.fr
</a>
. Dans le cas dun vote sur invitation nous vous suggérons de ne pas
dépasser 200 participants (le temps de création du vote peut prendre
quelques minutes).
</p>
<h4 className="bold mt-5">
Je rencontre un problème, comment obtenir de laide ?
</h4>
<p>
Si vous rencontrez un problème en utilisant notre application,
prenez contact avec nous par email à ladresse «
<a
href="mailto:app@mieuxvoter.fr?subject=[HELP]"
className="text-light"
>
app@mieuxvoter.fr
</a>
», et prenez soin de bien décrire le problème rencontré dans votre
message. Ajoutez éventuellement dans votre description le lien de
votre vote.
</p>
<h4 className="bold mt-5">
Y-a til une limite de votants appliquée pour les votes sur
invitation ?
</h4>
<p>
Le nombre maximum de votants pour un vote sur invitation est de 1000
personnes. Si toutefois votre besoin est supérieur à cette limite,
nous vous invitons à nous envoyer un email à ladresse «
<a href="mailto:contact@mieuxvoter.fr" className="text-light">
contact@mieuxvoter.fr
</a>
».
</p>
<h4 className="bold mt-5">
Combien de temps le lien vers la page de résultat reste-t-il actif ?
</h4>
<p>
Les liens fournis lors de la création de votre vote nont pas de
date dexpiration. Conservez-les précieusement afin de pouvoir
consulter les résultat dans le futur.
</p>
<h4 className="bold mt-5">
Comment puis-je massurer quune même personne ne vote pas deux
fois?
</h4>
<p>
Dans le cas dun vote sur invitation, seules les personnes dont le
courriel a été ajouté à la création du vote reçoivent une invitation
et peuvent donc voter. Chacune des invitations dispose dun lien
unique auquel est associé un jeton à usage unique. Ce jeton est
détruit aussitôt que la participation au vote de linvité est
enregistrée. Il garantit donc à lorganisateur que chaque
participant na pu voter quune seule fois.
</p>
<p>
Dans le cas dun vote public, toute personne peut participer à
lélection sil dispose du lien de lélection. Il ny a dans ce cas
aucune limite de soumission dun vote. Une même personne peut donc
voter plusieurs fois.
</p>
<h4 className="bold mt-5">
Lorsque jorganise une élection, puis-je connaître le nombre et
lidentité des votants?
</h4>
<p>
Le nombre de votants est indiqué sur la page de résultats de votre
élection. Lidentité des votants est quant à elle effacée, afin de
respecter les conditions dun vote démocratique lanonymat
garantit la sincérité des électeurs.
</p>
<h4 className="bold mt-5">Puis-je modifier mon vote ?</h4>
<p>
Une fois votre vote enregistré, vous ne pouvez plus le modifier. En
effet, votre vote étant anonymisé, ce qui nous empêche de faire le
lien entre vous et votre vote.
</p>
<h4 className="bold mt-5">
Comment puis-je récupérer un lien si je lai perdu ?
</h4>
<p>
Vous ne pouvez pas récupérer un lien pour voter après quil vous
soit communiquer. Gardez le précieusement. Cependant si vous avez le
lien pour voter, nous pouvons vous transmettre le lien des
résultats.
</p>
<h4 className="bold mt-5">
Comment interpréter les résultats dun vote au Jugement Majoritaire
?
</h4>
<p>
Les candidats ou propositions sont triées de la mention majoritaire
la plus favorable à la plus défavorable. En cas dégalité, on
calcule alors pour chaque candidat à départager: le pourcentage
délecteurs attribuant strictement plus que la mention majoritaire
commune et le pourcentage délecteurs attribuant strictement moins
que la mention majoritaire commune. La plus grande des 4 valeurs
détermine le résultat.
</p>
<h4 className="bold mt-5">Quelle sécurité pour mes données ?</h4>
<p>
Afin de garantir la sécurité de vos données, leur transmission est
chiffrée et vos votes sont anonymisés.
</p>
<h4 className="bold mt-5">
Que faites-vous des données collectées ?
</h4>
<p>
Lapplication app.mieuxvoter.fr a pour seul et unique but de faire
découvrir le vote au Jugement Majoritaire. Elle na pas de but
politique, ni commercial. Mieux Voter attache la plus grande
importance au strict respect de la vie privée, et utilise ces
données uniquement de manière responsable et confidentielle, dans
une finalité précise.
</p>
<h4 className="bold mt-5">Qui est Mieux Voter ?</h4>
<p>
« Mieux Voter » est une association loi 1901 qui promeut
lutilisation du Jugement Majoritaire, nouvelle théorie du choix
social, comme un outil pour améliorer les décisions collectives et
les exercices de démocratie participative à lusage de tous.
</p>
<h4 className="bold mt-5">
Comment nous aider à faire connaître le Jugement Majoritaire ?
</h4>
<p>
Vous avez apprécié votre expérience de vote démocratique au Jugement
Majoritaire ? <br />
Nous en sommes ravis ! Vous pouvez nous aider en faisant un don à
lassociation ici :
</p>
<Paypal btnColor="btn-success" className="mt-1" />
</Col>
</Row>
</Container>
);
};
export default FAQ;

@ -1,71 +0,0 @@
import { useState } from "react";
import Head from "next/head";
import Link from "next/link";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useTranslation } from "next-i18next";
import { Container, Row, Col, Button, Input } from "reactstrap";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faRocket } from "@fortawesome/free-solid-svg-icons";
import config from "../next-i18next.config.js";
export const getStaticProps = async ({ locale }) => ({
props: {
...(await serverSideTranslations(locale, [], config)),
},
});
const Home = () => {
const [title, setTitle] = useState(null);
const { t } = useTranslation();
return (
<Container>
<form autoComplete="off">
<Row>
<img
src="logos/logo-line-white.svg"
alt="logo of Mieux Voter"
height="128"
className="d-block ml-auto mr-auto mb-4"
/>
</Row>
<Row>
<Col className="text-center">
<h3>{t("common.valueProp")}</h3>
</Col>
</Row>
<Row className="mt-2">
<Col xs="12" md="9" xl="6" className="offset-xl-2">
<Input
placeholder={t("resource.writeQuestion")}
autoFocus
required
className="mt-2"
name="title"
value={title ? title : ""}
onChange={(e) => setTitle(e.target.value)}
maxLength="250"
/>
</Col>
<Col xs="12" md="3" xl="2">
<Link href={{ pathname: "/new/", query: { title: title } }}>
<Button
type="submit"
className="btn btn-block btn-secondary mt-2"
>
<FontAwesomeIcon icon={faRocket} className="mr-2" />
{t("resource.start")}
</Button>
</Link>
</Col>
</Row>
<Row className="mt-4">
<Col className="text-center">
<p>{t("resource.noAds")}</p>
</Col>
</Row>
</form>
</Container>
);
};
export default Home;

@ -1,81 +0,0 @@
import Link from "next/link";
import { Container, Row, Col } from "reactstrap";
import { useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import config from "../next-i18next.config.js";
export const getStaticProps = async ({ locale }) => ({
props: {
...(await serverSideTranslations(locale, [], config)),
},
});
const LegalNotices = (props) => {
const { t } = useTranslation();
return (
<Container>
<Row>
<Link href="/" className="d-block ml-auto mr-auto mb-4">
<img src="/logos/logo-line-white.svg" alt="logo" height="128" />
</Link>
</Row>
<Row className="mt-4">
<Col className="text-center">
<h1>{t("resource.legalNotices")}</h1>
</Col>
</Row>
<Row className="mt-4">
<Col>
<h3 className="bold">Editeur</h3>
<p>
Cette Application est éditée par lassociation loi 1901{" "}
<a
href="https://mieuxvoter.fr/"
target="_blank"
rel="noopener noreferrer"
className="text-white"
>
Mieux Voter
</a>
, dont le siège social est situé au 59 rue Saint-André des Arts, à
Paris (75006).
</p>
<p>
Adresse email :{" "}
<a href="mailto:app@mieuxvoter.fr" className="text-light">
app@mieuxvoter.fr
</a>
</p>
<p>
<b>Directeur de la publication</b>
<br />
Pierre-Louis Guhur
</p>
<h3 className="mt-2 bold">Hébergement</h3>
<ul>
<li>Base de données : Institut Systèmes Complexes, Paris ;</li>
<li>
Réseau de diffusion de contenu (CDN) : Netlify, 2325 3rd Street,
Suite 215, San Francisco, California 94107.
</li>
</ul>
<h3 className="mt-2 bold">&OElig;uvres graphiques</h3>
<p>
Les illustrations et graphismes sur cette application sont
l&oelig;uvre de lassociation Mieux Voter.
</p>
</Col>
</Row>
<Row className="mt-4">
<Col className="text-center">
<Link href="/" className="btn btn-secondary">
{t("common.backHomepage")}
</Link>
</Col>
</Row>
</Container>
);
};
export default LegalNotices;

@ -1,171 +0,0 @@
import { createRef } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import {
getDetails,
apiErrors,
ELECTION_NOT_STARTED_ERROR,
} from "@services/api";
import { Col, Container, Row } from "reactstrap";
import Link from "next/link";
import {
faCopy,
faVoteYea,
faExclamationTriangle,
faExternalLinkAlt,
faPollH,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import CopyField from "@components/CopyField";
import Error from "@components/Error";
import Facebook from "@components/banner/Facebook";
import config from "../../../next-i18next.config.js";
export async function getServerSideProps({ query: { pid }, locale }) {
let [details, translations] = await Promise.all([
getDetails(pid),
serverSideTranslations(locale, [], config),
]);
if (details.includes(ELECTION_NOT_STARTED_ERROR)) {
details = { title: "", on_invitation_only: true, restrict_results: true };
} else {
if (typeof details === "string" || details instanceof String) {
return { props: { err: details, ...translations } };
}
if (!details.title) {
return { props: { err: "Unknown error", ...translations } };
}
}
return {
props: {
invitationOnly: details.on_invitation_only,
restrictResults: details.restrict_results,
title: details.title,
pid: pid,
...translations,
},
};
}
const ConfirmElection = ({
title,
restrictResults,
invitationOnly,
pid,
err,
}) => {
const { t } = useTranslation();
if (err) {
return <Error value={apiErrors(err, t)} />;
}
const origin =
typeof window !== "undefined" && window.location.origin
? window.location.origin
: "http://localhost";
const urlVote = new URL(`/vote/${pid}`, origin);
const urlResult = new URL(`/result/${pid}`, origin);
const electionLink = invitationOnly ? (
<>
<p className="mb-1">
{t(
"Voters received a link to vote by email. Each link can be used only once!"
)}
</p>
</>
) : (
<>
<p className="mb-1">{t("Voting address")}</p>
<CopyField
value={urlVote.href}
iconCopy={faCopy}
iconOpen={faExternalLinkAlt}
t={t}
/>
</>
);
const fb = invitationOnly ? null : (
<Facebook
className="btn btn-sm btn-outline-light m-2"
text={t("Share election on Facebook")}
url={urlVote}
title={"app.mieuxvoter.fr"}
/>
);
const participate = invitationOnly ? null : (
<>
<Col className="col-lg-3 text-center mr-10">
<Link href={`/vote/${pid}`}>
<a target="_blank" rel="noreferrer" className="btn btn-success">
<FontAwesomeIcon icon={faVoteYea} className="mr-2" />
{t("resource.participateBtn")}
</a>
</Link>
</Col>
</>
);
return (
<Container>
<Head>
<title>{t("Successful election creation!")}</title>
<link rel="icon" href="/favicon.ico" />
<meta key="og:title" property="og:title" content={title} />
<meta
property="og:description"
key="og:description"
content={t("common.application")}
/>
</Head>
<Row className="mt-5">
<Col className="text-center offset-lg-3" lg="6">
<h2>{t("Successful election creation!")}</h2>
{fb}
</Col>
</Row>
<Row className="mt-5 mb-4">
<Col className="offset-lg-3" lg="6">
<h3 className="mb-3 text-center">{title}</h3>
<h5 className="mb-3 text-center">
<FontAwesomeIcon icon={faExclamationTriangle} className="mr-2" />
{t("Keep these links carefully")}
</h5>
<div className="border rounded p-4 pb-5">
{electionLink}
<p className="mt-4 mb-1">{t("Results address")}</p>
<CopyField
value={urlResult}
iconCopy={faCopy}
iconOpen={faExternalLinkAlt}
t={t}
/>
</div>
</Col>
</Row>
<Row className="mt-4 mb-4 justify-content-md-center">
{participate}
<Col className="text-center col-lg-3">
<Link href={`/result/${pid}`}>
<a target="_blank" rel="noreferrer" className="btn btn-secondary">
<FontAwesomeIcon icon={faPollH} className="mr-2" />
{t("resource.resultsBtn")}
</a>
</Link>
</Col>
</Row>
</Container>
);
};
export default ConfirmElection;

@ -1,557 +0,0 @@
import { useState, useEffect } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import {
Collapse,
Container,
Row,
Col,
Input,
Label,
InputGroup,
InputGroupAddon,
Button,
Card,
CardBody,
} from "reactstrap";
import { ReactMultiEmail, isEmail } from "react-multi-email";
import "react-multi-email/style.css";
import { toast, ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import queryString from "query-string";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faPlus,
faTrashAlt,
faCheck,
faCogs,
faExclamationTriangle,
} from "@fortawesome/free-solid-svg-icons";
import { useAppContext } from "@services/context";
import { createElection } from "@services/api";
import { translateGrades } from "@services/grades";
import HelpButton from "@components/form/HelpButton";
import Loader from "@components/wait";
import CandidatesField from "@components/form/CandidatesField";
import ConfirmModal from "@components/form/ConfirmModal";
import config from "../../next-i18next.config.js";
// Error messages
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());
// Convert a Date object into YYYY-MM-DD
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));
// 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 displayClockOptions = () =>
Array(24)
.fill(1)
.map((x, i) => (
<option value={i} key={i}>
{i}h00
</option>
));
export const getStaticProps = async ({ locale }) => ({
props: {
...(await serverSideTranslations(locale, [], config)),
},
});
const CreateElection = (props) => {
const { t } = useTranslation();
// default value : start at the last hour
const now = new Date();
const [title, setTitle] = useState("");
const [candidates, setCandidates] = useState([{ label: "" }, { label: "" }]);
const [numGrades, setNumGrades] = useState(5);
const [waiting, setWaiting] = useState(false);
const [isAdvancedOptionsOpen, setAdvancedOptionsOpen] = useState(false);
const [isTimeLimited, setTimeLimited] = useState(false);
const [restrictResult, setRestrictResult] = useState(false);
const [start, setStart] = useState(
new Date(now.getTime() - minutes(now) - seconds(now) - ms(now))
);
const [finish, setFinish] = useState(
new Date(start.getTime() + 7 * 24 * 3600 * 1000)
);
const [emails, setEmails] = useState([]);
// set the title on loading
const router = useRouter();
useEffect(() => {
if (!router.isReady) return;
const { title: urlTitle } = router.query;
setTitle(urlTitle || "");
}, [router.isReady]);
const handleIsTimeLimited = (event) => {
setTimeLimited(event.target.value === "1");
};
const handleRestrictResultCheck = (event) => {
setRestrictResult(event.target.value === "1");
};
const toggleAdvancedOptions = () => {
setAdvancedOptionsOpen(!isAdvancedOptionsOpen);
};
const addCandidate = () => {
if (candidates.length < 1000) {
candidates.push({ label: "" });
setCandidates(candidates);
}
};
const checkFields = () => {
if (!candidates) {
return { ok: false, msg: AT_LEAST_2_CANDIDATES_ERROR };
}
let numCandidates = 0;
candidates.forEach((c) => {
if (c.label !== "") numCandidates += 1;
});
if (numCandidates < 2) {
return { ok: false, msg: AT_LEAST_2_CANDIDATES_ERROR };
}
if (!title || title === "") {
return { ok: false, msg: NO_TITLE_ERROR };
}
return { ok: true, msg: "OK" };
};
const handleSubmit = () => {
const check = checkFields();
if (!check.ok) {
toast.error(t(check.msg), {
position: toast.POSITION.TOP_CENTER,
});
return;
}
setWaiting(true);
createElection(
title,
candidates.map((c) => c.label).filter((c) => c !== ""),
{
mails: emails,
numGrades,
start: start.getTime() / 1000,
finish: finish.getTime() / 1000,
restrictResult: restrictResult,
locale: router.locale.substring(0, 2).toLowerCase(),
},
(result) => {
if (result.id) {
router.push(`/new/confirm/${result.id}`);
} else {
toast.error(t("Unknown error. Try again please."), {
position: toast.POSITION.TOP_CENTER,
});
setWaiting(false);
}
}
);
};
const handleSendNotReady = (msg) => {
toast.error(t(msg), {
position: toast.POSITION.TOP_CENTER,
});
};
const check = checkFields();
const grades = translateGrades(t);
return (
<Container>
<Head>
<meta
key="og:title"
property="og:title"
content={t("common.application")}
/>
<meta
property="og:description"
key="og:description"
content={t("resource.valueProp")}
/>
</Head>
<ToastContainer />
{waiting ? <Loader /> : ""}
<form onSubmit={handleSubmit} autoComplete="off">
<Row>
<Col>
<h3>{t("resource.startVote")}</h3>
</Col>
</Row>
<hr />
<Row className="mt-4">
<Col xs="12">
<Label for="title">{t("resource.questionLabel")}</Label>
</Col>
<Col>
<Input
placeholder={t("resource.writeQuestionHere")}
tabIndex="1"
name="title"
id="title"
autoFocus
value={title}
onChange={(e) => setTitle(e.target.value)}
maxLength="250"
/>
</Col>
<Col xs="auto" className="align-self-center pl-0">
<HelpButton>
<u>{t("resource.eg")}</u> <em>{t("resource.exampleQuestion")}</em>
</HelpButton>
</Col>
</Row>
<Row className="mt-4">
<Col xs="12">
<Label for="title">{t("common.candidates")}</Label>
</Col>
<Col xs="12">
<CandidatesField onChange={setCandidates} />
</Col>
<Col
xs="12"
sm="6"
md="12"
className="text-center text-sm-right text-md-left"
>
<Button
color="link"
className="text-white mt-3 mb-1"
onClick={toggleAdvancedOptions}
>
<FontAwesomeIcon icon={faCogs} className="mr-2" />
{t("resource.advancedOptions")}
</Button>
</Col>
</Row>
<Collapse isOpen={isAdvancedOptionsOpen}>
<Card>
<CardBody className="text-primary">
<Row>
<Col xs="12" md="3" lg="3">
<Label for="title">{t("Access to results")}</Label>
</Col>
<Col xs="12" md="4" lg="3">
<Label className="radio " htmlFor="restrict_result_false">
<span className="small text-dark">{t("Immediately")}</span>
<input
className="radio"
type="radio"
name="restrict_result"
id="restrict_result_false"
onClick={handleRestrictResultCheck}
defaultChecked={!restrictResult}
value="0"
/>
<span className="checkround checkround-gray" />
</Label>
</Col>
<Col xs="12" md="4" lg="3">
<Label className="radio" htmlFor="restrict_result_true">
<span className="small">
<span className="text-dark">
{t("At the end of the election")}
</span>
<HelpButton className="ml-2">
{t(
"No one will be able to see the result until the end date is reached or until all participants have voted."
)}
</HelpButton>
</span>
<input
className="radio"
type="radio"
name="restrict_result"
id="restrict_result_true"
onClick={handleRestrictResultCheck}
defaultChecked={restrictResult}
value="1"
/>
<span className="checkround checkround-gray" />
</Label>
</Col>
</Row>
<hr className="mt-2 mb-2" />
<Row>
<Col xs="12" md="3" lg="3">
<Label for="title">{t("Voting time")}</Label>
</Col>
<Col xs="12" md="4" lg="3">
<Label className="radio " htmlFor="is_time_limited_false">
<span className="small text-dark">{t("Unlimited")}</span>
<input
className="radio"
type="radio"
name="time_limited"
id="is_time_limited_false"
onClick={handleIsTimeLimited}
defaultChecked={!isTimeLimited}
value="0"
/>
<span className="checkround checkround-gray" />
</Label>
</Col>
<Col xs="12" md="4" lg="3">
<Label className="radio" htmlFor="is_time_limited_true">
<span className="small">
<span className="text-dark">{t("Defined period")}</span>
</span>
<input
className="radio"
type="radio"
name="time_limited"
id="is_time_limited_true"
onClick={handleIsTimeLimited}
defaultChecked={isTimeLimited}
value="1"
/>
<span className="checkround checkround-gray" />
</Label>
</Col>
</Row>
<div
className={
(isTimeLimited ? "d-block " : "d-none") + " bg-light p-3"
}
>
<Row>
<Col xs="12" md="3" lg="3">
<span className="label">- {t("Starting date")}</span>
</Col>
<Col xs="6" md="4" lg="3">
<input
className="form-control"
type="date"
value={dateToISO(start)}
onChange={(e) => {
setStart(
new Date(
timeMinusDate(start) +
new Date(e.target.valueAsNumber).getTime()
)
);
}}
/>
</Col>
<Col xs="6" md="5" lg="3">
<select
className="form-control"
value={getOnlyValidDate(start).getHours()}
onChange={(e) =>
setStart(
new Date(
dateMinusTime(start).getTime() +
e.target.value * 3600000
)
)
}
>
{displayClockOptions()}
</select>
</Col>
</Row>
<Row className="mt-2">
<Col xs="12" md="3" lg="3">
<span className="label">- {t("Ending date")}</span>
</Col>
<Col xs="6" md="4" lg="3">
<input
className="form-control"
type="date"
value={dateToISO(finish)}
min={dateToISO(start)}
onChange={(e) => {
setFinish(
new Date(
timeMinusDate(finish) +
new Date(e.target.valueAsNumber).getTime()
)
);
}}
/>
</Col>
<Col xs="6" md="5" lg="3">
<select
className="form-control"
value={getOnlyValidDate(finish).getHours()}
onChange={(e) =>
setFinish(
new Date(
dateMinusTime(finish).getTime() +
e.target.value * 3600000
)
)
}
>
{displayClockOptions()}
</select>
</Col>
</Row>
</div>
<hr className="mt-2 mb-2" />
<Row>
<Col xs="12" md="3" lg="3">
<span className="label">{t("Grades")}</span>
</Col>
<Col xs="10" sm="11" md="4" lg="3">
<select
className="form-control"
tabIndex={candidates.length + 3}
onChange={(e) => setNumGrades(e.target.value)}
defaultValue="5"
>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
</select>
</Col>
<Col xs="auto" className="align-self-center pl-0 ">
<HelpButton>
{t(
"You can select here the number of grades for your election"
)}
<br />
<u>{t("For example:")}</u>{" "}
<em>
{" "}
{t("5 = Excellent, Very good, Good, Fair, Passable")}
</em>
</HelpButton>
</Col>
<Col
xs="12"
md="9"
lg="9"
className="offset-xs-0 offset-md-3 offset-lg-3"
>
{grades.map((mention, i) => {
return (
<span
key={i}
className="badge badge-light mr-2 mt-2 "
style={{
backgroundColor: mention.color,
color: "#fff",
opacity: i < numGrades ? 1 : 0.3,
}}
>
{mention.label}
</span>
);
})}
</Col>
</Row>
<hr className="mt-2 mb-2" />
<Row>
<Col xs="12" md="3" lg="3">
<span className="label">{t("Participants")}</span>
</Col>
<Col xs="12" md="9" lg="9">
<ReactMultiEmail
placeholder={t("Add here participants' emails")}
emails={emails}
onChange={setEmails}
validateEmail={(email) => {
return isEmail(email); // return boolean
}}
getLabel={(email, index, removeEmail) => {
return (
<div data-tag key={index}>
{email}
<span
data-tag-handle
onClick={() => removeEmail(index)}
>
×
</span>
</div>
);
}}
/>
<div>
<small className="text-muted">
{t(
"If you list voters' emails, only them will be able to access the election"
)}
</small>
</div>
</Col>
</Row>
<hr className="mt-2 mb-2" />
</CardBody>
</Card>
</Collapse>
<Row className="justify-content-end mt-2">
<Col xs="12" md="3">
{check.ok ? (
<ConfirmModal
title={title}
candidates={candidates}
isTimeLimited={isTimeLimited}
start={start}
finish={finish}
emails={emails}
restrictResult={restrictResult}
grades={grades.slice(0, numGrades)}
className={"btn btn-success float-right btn-block"}
tabIndex={candidates.length + 1}
confirmCallback={handleSubmit}
/>
) : (
<Button
type="button"
className="btn btn-dark float-right btn-block"
onClick={handleSendNotReady}
>
<FontAwesomeIcon icon={faCheck} className="mr-2" />
{t("Confirm")}
</Button>
)}
</Col>
</Row>
</form>
</Container>
);
};
export default CreateElection;

@ -1,101 +0,0 @@
import { Col, Container, Row } from "reactstrap";
import { useTranslation } from "next-i18next";
import Link from "next/link";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import config from "../next-i18next.config.js";
export const getStaticProps = async ({ locale }) => ({
props: {
...(await serverSideTranslations(locale, [], config)),
},
});
const PrivacyPolicy = (props) => {
const { t } = useTranslation();
return (
<Container>
<Row>
<Link href="/" className="d-block ml-auto mr-auto mb-4">
<img src="/logos/logo-line-white.svg" alt="logo" height="128" />
</Link>
</Row>
<Row className="mt-4">
<Col className="text-center">
<h1>{t("Privacy policy")}</h1>
</Col>
</Row>
<Row className="mt-4">
<Col>
<p className="text-center">
Dernière mise à jour de notre politique de confidentialité effectuée
le 27 avril 2020.
</p>
<h4 className="bold mt-5">Introduction</h4>
<p>
Dans le cadre de la mise à disposition de son application web de
vote au jugement majoritaire, accessible sur Internet à ladresse
app.mieuxvoter.fr, ci-après lApplication, lassociation loi 1901 «
Mieux Voter » , dont le siège social est situé au 59 rue saint andré
des arts, à Paris (75006), ci-après lAssociation, est amenée à
collecter et à traiter des informations dont certaines sont
qualifiées de « Données personnelles » . Mieux Voter attache la plus
grande importance au respect de la vie privée, et utilise ces
données uniquement de manière responsable et confidentielle et dans
une finalité précise.
</p>
<h4 className="bold mt-5">Notre politique de confidentialité</h4>
<p>
La présente politique de confidentialité détaille les conditions
dutilisation et de traitement par lAssociation des Données
personnelles (ci-après définies) collectées via lApplication.
LAssociation sengage à respecter les dispositions de la loi
n°78-17 du 6 janvier 1978 relative à linformatique, aux fichiers et
aux libertés modifiée et au Règlement (UE) 2016/679 du Parlement
européen et du Conseil du 27 avril 2016 dit « RGPD » et prendre
toute précaution nécessaire pour préserver la sécurité des Données
personnelles confiées.
</p>
<h4 className="bold mt-5">Responsable de traitement</h4>
<p>
En qualité de responsable de traitement, lAssociation peut traiter
les Données personnelles.
</p>
<h4 className="bold mt-5">
Données personnelles traitées et finalités de traitement
</h4>
<p>
LAssociation recueille sur lApplication les Données personnelles
dans une finalité précise. Ces données sont nécessaires à la
fourniture de notre service. Dans le cadre de la fourniture de ce
service, lAssociation traite uniquement les données personnelles
suivantes (définies comme les « Données personnelles ») strictement
nécessaires à la fourniture du service :
</p>
<ul>
<li> Les emails des personnes invitées à un vote</li>
</ul>
<p>
{" "}
La finalité de traitement de ces données personnelles est de
permettre à lAssociation de fournir le service. Ces données sont
traitées au moment de la création du vote pour envoyer les
invitations et détruites aussitôt les invitations envoyées. Elles ne
sont jamais stockées sur nos serveurs.
</p>
<h4 className="bold mt-5">Sécurité des Données personnelles</h4>
<p>
LAssociation sengage, au titre de son obligation de moyens, à
prendre toutes les précautions utiles et met en œuvre des mesures
techniques et organisationnelles appropriées en la matière pour
garantir un niveau de sécurité adapté et pour protéger les Données
personnelles contre les altérations, destructions et accès non
autorisés.
</p>
</Col>
</Row>
</Container>
);
};
export default PrivacyPolicy;

@ -1,333 +0,0 @@
import { useState } from "react";
import Head from "next/head";
import { useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useRouter } from "next/router";
import {
Container,
Row,
Col,
Collapse,
Card,
CardHeader,
CardBody,
Table,
} from "reactstrap";
import { getResults, getDetails, apiErrors } from "@services/api";
import { grades } from "@services/grades";
import { translateGrades } from "@services/grades";
import Facebook from "@components/banner/Facebook";
import Error from "@components/Error";
import config from "../../../next-i18next.config.js";
export async function getServerSideProps({ query, locale }) {
const { pid, tid } = query;
const [res, details, translations] = await Promise.all([
getResults(pid),
getDetails(pid),
serverSideTranslations(locale, [], config),
]);
if (typeof res === "string" || res instanceof String) {
return { props: { err: res.slice(1, -1), ...translations } };
}
if (typeof details === "string" || details instanceof String) {
return { props: { err: res.slice(1, -1), ...translations } };
}
if (!details.candidates || !Array.isArray(details.candidates)) {
return { props: { err: "Unknown error", ...translations } };
}
return {
props: {
title: details.title,
numGrades: details.num_grades,
candidates: res,
pid: pid,
...translations,
},
};
}
const Result = ({ candidates, numGrades, title, pid, err }) => {
const { t } = useTranslation();
if (err && err !== "") {
return <Error value={apiErrors(err, t)} />;
}
const router = useRouter();
const allGrades = translateGrades(t);
const grades = allGrades.filter(
(grade) => grade.value >= allGrades.length - numGrades
);
const offsetGrade = grades.length - numGrades;
const colSizeCandidateLg = 4;
const colSizeCandidateMd = 6;
const colSizeCandidateXs = 12;
const colSizeGradeLg = 1;
const colSizeGradeMd = 1;
const colSizeGradeXs = 1;
const origin =
typeof window !== "undefined" && window.location.origin
? window.location.origin
: "http://localhost";
console.log("origin", origin);
const urlVote = new URL(`/vote/${pid}`, origin);
const [collapseProfiles, setCollapseProfiles] = useState(false);
const [collapseGraphics, setCollapseGraphics] = useState(false);
const sum = (seq) => Object.values(seq).reduce((a, b) => a + b, 0);
const numVotes =
candidates && candidates.length > 0 ? sum(candidates[0].profile) : 1;
const gradeIds =
candidates && candidates.length > 0
? Object.keys(candidates[0].profile)
: [];
return (
<Container>
<Head>
<title>{title}</title>
<link rel="icon" href="/favicon.ico" />
<meta property="og:title" content={title} />
</Head>
<Row>
<Col xs="12">
<h3>{title}</h3>
</Col>
</Row>
<Row className="mt-5">
<Col>
<ol className="result">
{candidates.map((candidate, i) => {
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: grades.slice(0).reverse()[
candidate.grade
].color,
color: "#fff",
}}
>
{allGrades.slice(0).reverse()[gradeValue].label}
</span>
</li>
);
})}
</ol>
<h5>
<small>
{t("resource.numVotes")}
{" " + numVotes}
</small>
</h5>
</Col>
</Row>
<Row className="mt-5">
<Col>
<Card className="bg-light text-primary">
<CardHeader
className="pointer"
onClick={() => setCollapseGraphics(!collapseGraphics)}
>
<h4
className={
"m-0 panel-title " + (collapseGraphics ? "collapsed" : "")
}
>
{t("Graph")}
</h4>
</CardHeader>
<Collapse isOpen={collapseGraphics}>
<CardBody className="pt-5">
<div>
<div
className="median"
style={{ height: candidates.length * 28 + 30 }}
/>
<table style={{ width: "100%" }}>
<tbody>
{candidates.map((candidate, i) => {
return (
<tr key={i}>
<td style={{ width: "30px" }}>{i + 1}</td>
{/*candidate.label*/}
<td>
<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";
}
return (
<td
key={i}
style={{
width: percent,
backgroundColor:
grades[i].color,
}}
>
&nbsp;
</td>
);
} else {
return null;
}
})}
</tr>
</tbody>
</table>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
<div className="mt-4">
<small>
{candidates.map((candidate, i) => {
return (
<span key={i}>
{i > 0 ? ", " : ""}
<b>{i + 1}</b>: {candidate.name}
</span>
);
})}
</small>
</div>
<div className="mt-2">
<small>
{grades.map((grade, i) => {
return (
<span
key={i}
className="badge badge-light mr-2 mt-2"
style={{
backgroundColor: grade.color,
color: "#fff",
}}
>
{grade.label}
</span>
);
})}
</small>
</div>
</CardBody>
</Collapse>
</Card>
</Col>
</Row>
<Row className="mt-3">
<Col>
<Card className="bg-light text-primary">
<CardHeader
className="pointer"
onClick={() => setCollapseProfiles(!collapseProfiles)}
>
<h4
className={
"m-0 panel-title " + (collapseProfiles ? "collapsed" : "")
}
>
{t("Preference profile")}
</h4>
</CardHeader>
<Collapse isOpen={collapseProfiles}>
<CardBody>
<div className="table-responsive">
<Table className="profiles">
<thead>
<tr>
<th>#</th>
{grades.map((grade, i) => {
return (
<th key={i}>
<span
className="badge badge-light"
style={{
backgroundColor: grade.color,
color: "#fff",
}}
>
{grade.label}{" "}
</span>
</th>
);
})}
</tr>
</thead>
<tbody>
{candidates.map((candidate, i) => {
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>;
})}
</tr>
);
})}
</tbody>
</Table>
</div>
<small>
{candidates.map((candidate, i) => {
return (
<span key={i}>
{i > 0 ? ", " : ""}
<b>{i + 1}</b>: {candidate.name}
</span>
);
})}
</small>
</CardBody>
</Collapse>
</Card>
</Col>
</Row>
<Row>
<Col xs="12" className="text-center pt-2 pb-5">
<Facebook
className="btn btn-outline-light m-2"
text={t("Share results on Facebook")}
url={urlVote}
title={title}
/>
</Col>
</Row>
</Container>
);
};
export default Result;

@ -1,271 +0,0 @@
import { useState } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useTranslation } from "next-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 { getDetails, castBallot, apiErrors } from "@services/api";
import Error from "@components/Error";
import { translateGrades } from "@services/grades";
import config from "../../../next-i18next.config.js";
const shuffle = (array) => array.sort(() => Math.random() - 0.5);
export async function getServerSideProps({ query: { pid, tid }, locale }) {
const [details, translations] = await Promise.all([
getDetails(pid),
serverSideTranslations(locale, [], config),
]);
if (typeof details === "string" || details instanceof String) {
return { props: { err: details, ...translations } };
}
if (!details.candidates || !Array.isArray(details.candidates)) {
return { props: { err: "Unknown error", ...translations } };
}
shuffle(details.candidates);
return {
props: {
...translations,
invitationOnly: details.on_invitation_only,
restrictResults: details.restrict_results,
candidates: details.candidates.map((name, i) => ({ id: i, label: name })),
title: details.title,
numGrades: details.num_grades,
pid: pid,
token: tid || null,
},
};
}
const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => {
const { t } = useTranslation();
if (err) {
return <Error value={apiErrors(err, t)}></Error>;
}
const [judgments, setJudgments] = useState([]);
const colSizeCandidateLg = 4;
const colSizeCandidateMd = 6;
const colSizeCandidateXs = 12;
const colSizeGradeLg = Math.floor((12 - colSizeCandidateLg) / numGrades);
const colSizeGradeMd = Math.floor((12 - colSizeCandidateMd) / numGrades);
const colSizeGradeXs = Math.floor((12 - colSizeCandidateXs) / numGrades);
const router = useRouter();
const allGrades = translateGrades(t);
const grades = allGrades.filter(
(grade) => grade.value >= allGrades.length - numGrades
);
const handleGradeClick = (event) => {
let data = {
id: parseInt(event.currentTarget.getAttribute("data-id")),
value: parseInt(event.currentTarget.value),
};
//remove candidate
const newJudgments = judgments.filter(
(judgment) => judgment.id !== data.id
);
newJudgments.push(data);
setJudgments(newJudgments);
};
const handleSubmitWithoutAllRate = () => {
toast.error(t("You have to judge every candidate/proposal!"), {
position: toast.POSITION.TOP_CENTER,
});
};
const handleSubmit = (event) => {
event.preventDefault();
const gradesById = {};
judgments.forEach((c) => {
gradesById[c.id] = c.value;
});
const gradesByCandidate = [];
Object.keys(gradesById).forEach((id) => {
gradesByCandidate.push(gradesById[id]);
});
castBallot(gradesByCandidate, pid, token, () => {
router.push(`/vote/${pid}/confirm`);
});
};
return (
<Container>
<Head>
<title>{title}</title>
<title>{title}</title>
<meta key="og:title" property="og:title" content={title} />
<meta
property="og:description"
key="og:description"
content={t("common.application")}
/>
</Head>
<ToastContainer />
<form onSubmit={handleSubmit} autoComplete="off">
<Row>
<Col>
<h3>{title}</h3>
</Col>
</Row>
<Row className="cardVote d-none d-lg-flex">
<Col
xs={colSizeCandidateXs}
md={colSizeCandidateMd}
lg={colSizeCandidateLg}
>
<h5>&nbsp;</h5>
</Col>
{grades.map((grade, gradeId) => {
return gradeId < numGrades ? (
<Col
xs={colSizeGradeXs}
md={colSizeGradeMd}
lg={colSizeGradeLg}
key={gradeId}
className="text-center p-0"
style={{ lineHeight: 2 }}
>
<small
className="nowrap bold badge"
style={{ backgroundColor: grade.color, color: "#fff" }}
>
{grade.label}
</small>
</Col>
) : null;
})}
</Row>
{candidates.map((candidate, candidateId) => {
return (
<Row key={candidateId} className="cardVote">
<Col
xs={colSizeCandidateXs}
md={colSizeCandidateMd}
lg={colSizeCandidateLg}
>
<h5 className="m-0">{candidate.label}</h5>
<hr className="d-lg-none" />
</Col>
{grades.map((grade, gradeId) => {
console.assert(gradeId < numGrades);
const gradeValue = grade.value;
return (
<Col
xs={colSizeGradeXs}
md={colSizeGradeMd}
lg={colSizeGradeLg}
key={gradeId}
className="text-lg-center"
>
<label
htmlFor={
"candidateGrade" + candidateId + "-" + gradeValue
}
className="check"
>
<small
className="nowrap d-lg-none ml-2 bold badge"
style={
judgments.find((judgment) => {
return (
JSON.stringify(judgment) ===
JSON.stringify({
id: candidate.id,
value: gradeValue,
})
);
})
? { backgroundColor: grade.color, color: "#fff" }
: {
backgroundColor: "transparent",
color: "#000",
}
}
>
{grade.label}
</small>
<input
type="radio"
name={"candidate" + candidateId}
id={"candidateGrade" + candidateId + "-" + gradeValue}
data-index={candidateId}
data-id={candidate.id}
value={grade.value}
onClick={handleGradeClick}
defaultChecked={judgments.find((element) => {
return (
JSON.stringify(element) ===
JSON.stringify({
id: candidate.id,
value: gradeValue,
})
);
})}
/>
<span
className="checkmark"
style={
judgments.find(function (judgment) {
return (
JSON.stringify(judgment) ===
JSON.stringify({
id: candidate.id,
value: gradeValue,
})
);
})
? { backgroundColor: grade.color, color: "#fff" }
: {
backgroundColor: "transparent",
color: "#000",
}
}
/>
</label>
</Col>
);
})}
</Row>
);
})}
<Row>
<Col className="text-center">
{judgments.length !== candidates.length ? (
<Button
type="button"
onClick={handleSubmitWithoutAllRate}
className="btn btn-dark "
>
<FontAwesomeIcon icon={faCheck} className="mr-2" />
{t("Submit my vote")}
</Button>
) : (
<Button type="submit" className="btn btn-success ">
<FontAwesomeIcon icon={faCheck} className="mr-2" />
{t("Submit my vote")}
</Button>
)}
</Col>
</Row>
</form>
</Container>
);
};
export default VoteBallot;

@ -1,79 +0,0 @@
import Head from "next/head";
import { Col, Container, Row } from "reactstrap";
import Link from "next/link";
import { useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Paypal from "@components/banner/Paypal";
import Gform from "@components/banner/Gform";
import Error from "@components/Error";
import { getDetails, apiErrors } from "@services/api";
import config from "../../../next-i18next.config.js";
export async function getServerSideProps({ query: { pid }, locale }) {
const [details, translations] = await Promise.all([
getDetails(pid),
serverSideTranslations(locale, [], config),
]);
if (typeof details === "string" || details instanceof String) {
return { props: { err: res.slice(1, -1), ...translations } };
}
if (!details.candidates || !Array.isArray(details.candidates)) {
return { props: { err: "Unknown error", ...translations } };
}
return {
props: {
...translations,
invitationOnly: details.on_invitation_only,
restrictResults: details.restrict_results,
candidates: details.candidates.map((name, i) => ({ id: i, label: name })),
title: details.title,
numGrades: details.num_grades,
pid: pid,
},
};
}
const VoteSuccess = ({ title, invitationOnly, pid, err }) => {
const { t } = useTranslation();
if (err && err !== "") {
return <Error value={apiErrors(err, t)} />;
}
return (
<Container>
<Head>
<title>{t("resource.voteSuccess")}</title>
<link rel="icon" href="/favicon.ico" />
<meta key="og:title" property="og:title" content={title} />
<meta
property="og:description"
key="og:description"
content={t("common.application")}
/>
</Head>
<Row>
<Link href="/">
<a className="d-block ml-auto mr-auto mb-4">
<img src="/logos/logo-line-white.svg" alt="logo" height="128" />
</a>
</Link>
</Row>
<Row className="mt-4">
<Col className="text-center offset-lg-3" lg="6">
<h2>{t("resource.voteSuccess")}</h2>
<p>{t("resource.thanks")}</p>
<div className="mt-3">
<Gform className="btn btn-secondary" />
</div>
<div className="mt-5">
<Paypal btnColor="btn-success" />
</div>
</Col>
</Row>
</Container>
);
};
export default VoteSuccess;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

@ -1,100 +0,0 @@
{
"title": "Plattform mit Mehrheitswahl",
"Homepage": "Homepage",
"Source code": "Quellcode",
"Who are we?": "Wer wir sind?",
"Privacy policy": "Datenschutzerklärung",
"resource.legalNotices": "Rechtliche Hinweise",
"FAQ": "FAQ",
"resource.help": "Brauchen Sie Hilfe?",
"BetterVote": " BetterVote",
"Voting platform": "Wahlplattform",
"Majority Judgment": " Mehrheitswahl ",
"Start an election": "Eine Wahl beginnen",
"resource.candidatePlaceholder": "Name des Kandidaten/Abstimmungsvorschlags",
"Delete?": "Löschen?",
"Are you sure to delete": "Sind Sie sich sicher, dass Sie dies löschen möchten?",
"the row": "die Zeile",
"Write here your question or introduce simple your election (250 characters max.)": "Schreiben Sie hier Ihre Frage oder erklären Sie kurz ihre Wahl (bis 250 Zeichen)",
"Enter the name of your candidate or proposal here (250 characters max.)": "Geben Sie hier den Namen Ihres Kandidaten oder Antrags ein (max. 250 Zeichen)",
"Please add at least 2 candidates.": "Bitte geben Sie mindestens zwei Kandidaten vor. ",
"Question of the election": "Zur Wahl stehende Frage",
"Write here the question of your election": "Schreiben Sie hier die zur Wahl stehenden Frage",
"For example:": "Zum Beispiel",
"For the role of my representative, I judge this candidate...": "Meine Einschätzung des Kandidaten als meinen Repräsentanten ist …",
"Candidates/Proposals": "Kandidaten/Abstimmungsvorschlag ",
"Add a proposal": "Weiteren hinzufügen",
"Advanced options": "Weitere Optionen",
"Starting date": "Anfangsdatum",
"Ending date": "Enddatum",
"Defined period" : "Definierte Periode",
"Unlimited" : "Unbegrenzt",
"Voting time" : "Abstimmungszeit",
"Grades": "Note",
"You can select here the number of grades for your election": " Sie können hier die Anzahl der Noten für Ihre Wahl auswählen ",
"5 = Excellent, Very good, Good, Fair, Passable": "5 = hervorragend, sehr gut, gut, befriedigend, ausreichend",
"Participants": "Teilnehmer",
"Add here participants' emails": "Fügen Sie hier die Email Adressen der Teilnehmer hinzu.",
"List voters' emails in case the election is not opened": "Falls die Wahl noch nicht sofort geöffnet werden soll, fügen Sie die Email Adressen der Teilnehmer hier zu.",
"Validate": "Ok",
"Submit my vote": "Ok",
"Confirm your vote": "Bestätigen Sie Ihre Wahl",
"The form contains no address.": "Keine Email Adresse wurde hinzugefügt.",
"The election will be opened to anyone with the link": "Die Wahl ist offen afür jeden, der diesen Link hat.",
"Start the election": "Mit der Wahl beginnen.",
"Cancel": "Abbrechen",
"Confirm": "Ok",
"Successful election creation!": "Die Wahl wurde erfolgreich erstellt!",
"You can now share the election link to participants:": "Sie können nun den Teilnehmern den Link zukommen lassen.",
"Copy": "Kopieren",
"Here is the link for the results in real time:": " Hier ist der Link für die Ergebnisse in Echtzeit:",
"Keep these links carefully": "Speichern Sie diesen Link an einem sicheren Ort.",
"resource.participateBtn": "Machen Sie jetzt mit!",
"t": "<0>Achtung</0> : Sie werden zu einem späteren Zeitpunkt keine Möglichkeit haben, diese Links abzurufen, auch wir haben keinen Zugriff darauf. Sie können aber beispielsweise diese Seite in Ihrem Browser als Lesezeichen speichern.",
"Simple and free: organize an election with Majority Judgment.": "Einfach und kostenlos: Organisation von Mehrheitswahlen.",
"Start": "Start",
"No advertising or ad cookies": "Keine Werbung und auch keine Cookies zu Werbezwecken.",
"Oops! This election does not exist or it is not available anymore.": "Ups! Diese Wahl existiert nicht oder ist nicht mehr verfügbar. ",
"You can start another election.": "Sie können eine neue Umfrage starten.",
"Go back to homepage": "Zurück zur Hompage",
"You have to judge every candidate/proposal!": "Sie müssen jeden Kandidaten/Abstimmungsvorschlag bewerten!",
"resource.voteSuccess": " Ihre Teilnahme wurde gespeichert!",
"resource.thanks": " Vielen Dank für Ihre Teilnahme.",
"Support us !": "Unterstützen Sie uns!",
"PayPal - The safer, easier way to pay online!": "PayPal - Die sicherere und einfachere Art, online zu bezahlen!",
"resource.numVotes": "Anzahl der Stimmen:",
"Unknown error. Try again please.": "Unbekannter Fehler. Bitte versuchen Sie es erneut.",
"Ending date:": "Enddatum:",
"If you list voters' emails, only them will be able to access the election": "Wenn Sie die E-Mails der Wähler auflisten, haben nur diese Zugriff auf die Wahl",
"Dates": "Datum",
"The election will take place from": "Die Wahl findet von",
"at": "um",
"to": "bis",
"Voters' list": "Wählerliste",
"Voters received a link to vote by email. Each link can be used only once!": "Die Wähler erhielten per E-Mail einen Link zur Stimmabgabe. Jeder Link kann nur einmal verwendet werden!",
"Results of the election:": "Ergebnisse der Wahl",
"Graph": "Grafik",
"Preference profile": "Präferenz-Profil",
"Oops... The election is unknown.": "Hoppla... Die Wahl ist unbekannt.",
"The election is still going on. You can't access now to the results.": "Die Wahl dauert noch an. Sie können jetzt nicht auf die Ergebnisse zugreifen.",
"No votes have been recorded yet. Come back later.": "Es wurden noch keine Abstimmungen registriert. Kommen Sie später wieder.",
"The election has not started yet.": "Die Wahlen haben noch nicht begonnen.",
"The election is over. You can't vote anymore": "Die Wahl ist vorbei. Sie können nicht mehr wählen",
"You need a token to vote in this election": "Sie brauchen eine Wertmarke, um an dieser Wahl teilzunehmen",
"You seem to have already voted.": "Sie scheinen bereits abgestimmt zu haben.",
"The parameters of the election are incorrect.": "Die Parameter der Wahl sind falsch.",
"Access to results" : "Zugang zu den Ergebnissen",
"Immediately": "Sofort",
"At the end of the election": "Am Ende der Wahl",
"Results available at the close of the vote": "Ergebnisse am Ende der Abstimmung verfügbar",
"The results page will not be accessible until all participants have voted.":"Die Ergebnisseite wird nicht zugänglich sein, bis alle Teilnehmer abgestimmt haben.",
"The results page will not be accessible until the end date is reached.": "Die Ergebnisseite wird nicht zugänglich sein, bis das Enddatum erreicht ist.",
"No one will be able to see the result until the end date is reached or until all participants have voted.": "Niemand wird das Ergebnis sehen können, bis das Enddatum erreicht ist oder bis alle Teilnehmer abgestimmt haben.",
"Send me this link" : "Senden Sie mir diesen Link",
"Send me these links" : "Schicken Sie mir diesen Link",
"Open" : "Öffnen Sie",
"Voting address" : "Abstimmungs-URL",
"Results address" : "Ergebnis-URL",
"Share election on Facebook" : "Wahl auf Facebook teilen",
"Share results on Facebook" : "Ergebnisse auf Facebook teilen"
}

@ -1,8 +0,0 @@
{
"common.vote": "Vote!",
"common.mieuxvoter": "Better Vote",
"common.helpus": "Do you want to help us?",
"common.valueProp": "Simple and free: organise a vote with Majority Judgment.",
"common.candidates": "Candidates/Proposals",
"common.backHomepage": "Back to home page"
}

@ -1,10 +0,0 @@
{
"email.hello": "Hi, there! 🙂",
"email.why": "This email was sent to you because your email was filled out to participate in the vote on the subject:",
"email.linkVote": "The link for the vote is as follows:",
"email.linkResult": "The link that will give you the results when they are available is as follows:",
"email.happy": "We are happy to send you this email! You will be able to vote using majority judgment.",
"email.copyLink": "If that doesn't work, copy and paste the following link into your browser:",
"email.aboutjm": "If you require any further information, please visit our site.",
"email.bye": "Good vote! 🤗"
}

@ -1,11 +0,0 @@
{
"error.e1": "Oops... The election is unknown",
"error.e2": "The election is still going on. You can't access now to the results.",
"error.e3": "No votes have been recorded yet. Come back later.",
"error.e4": "The election has not started yet.",
"error.e5": "The election is over. You can't vote anymore",
"error.e6": "You need a token to vote in this election",
"error.e7": "You seem to have already voted.",
"error.e8": "The parameters of the election are incorrect.",
"error.catch22": "Unknown error"
}

@ -1,107 +0,0 @@
{
"title": "Application for Majority Judgment",
"Homepage": "Homepage",
"Source code": "Source code",
"Who are we?": "Who are we?",
"Privacy policy": "Privacy policy",
"resource.legalNotices": "Legal notices",
"FAQ": "FAQ",
"resource.help": "Need help?",
"BetterVote": "BetterVote",
"Voting platform": "Voting platform",
"Majority Judgment": "Majority Judgment",
"Start an election": "Start a vote",
"Candidate/proposal ...": "Candidate/proposal...",
"Delete?": "Confirm deletion",
"Are you sure to delete": "Are you sure you want to delete",
"the row": "the row",
"resource.candidatePlaceholder": "Candidates or proposal's name",
"resource.writeQuestionHere": "Write here your question or describe your vote (max. 250 characters)",
"Enter the name of your candidate or proposal here (250 characters max.)": "Enter your proposal or the name of your candidate (max. 250 characters)",
"Please add at least 2 candidates.": "Please add at least 2 candidates.",
"resource.questionLabel": "Question of the vote",
"resource.writeQuestion": "Write here your question or describe your vote",
"resource.eg": "For example:",
"resource.exampleQuestion": "For the role of my representative, I think this candidate is...",
"Add a proposal": "Add a candidate/proposal",
"resource.advancedOptions": "Advanced options",
"Starting date": "Start date",
"Ending date": "End date",
"Defined period": "Defined period",
"Unlimited": "Unlimited",
"Voting time": "Voting time",
"Grades": "Mentions",
"You can select here the number of grades for your election": "Select here the number of mentions of your vote",
"5 = Excellent, Very good, Good, Fair, Passable": "5 = Excellent, Very good, Good, Fair, Poor",
"Participants": "Participants",
"Add here participants' emails": "Add here participants' emails",
"List voters' emails in case the election is not opened": "List participants' emails in case the election is not open",
"Validate": "Confirm",
"Submit my vote": "Submit my vote",
"Confirm your vote": "Confirm your vote",
"The form contains no address.": "The form contains no email addresses.",
"The election will be opened to anyone with the link": "The vote will be open to anyone with the link",
"resource.startVote": "Start the vote",
"Cancel": "Cancel",
"Confirm": "Confirm",
"Successful election creation!": "Vote successfully created!",
"You can now share the election link to participants:": "You can now share the voting link to participants:",
"Copy": "Copy",
"Here is the link for the results in real time:": "Here is the link for real-time results:",
"Keep these links carefully": "Keep these links carefully",
"resource.participateBtn": "Participate now!",
"resource.resultsBtn": "Go to results",
"t": "<0>Warning</0>: you will have no possibility to recover these links, and we will not be able to share them with you. For safekeeping, you can bookmark them in your browser.",
"resource.start": "Start",
"resource.noAds": "No advertising or ad cookies",
"Oops! This election does not exist or it is not available anymore.": "Oops! This vote does not exist or is no longer available.",
"You can start another election.": "You can start another vote.",
"Go back to homepage": "Go back to homepage",
"You have to judge every candidate/proposal!": "Please assess every candidate/proposal.",
"resource.voteSuccess": "Your participation was successfully recorded!",
"resource.thanks": "Thank your for your participation.",
"Ending date:": "Ending date:",
"Excellent": "Excellent",
"Very good": "Very good",
"Good": "Good",
"Fair": "Fair",
"Passable": "Poor",
"Insufficient": "Insufficient",
"To reject": "To be rejected",
"Dates": "Dates",
"The election will take place from": "The vote will take place from",
"at": "at",
"to": "to",
"Voters' list": "List of participants' emails",
"Graph": "Graph",
"Preference profile": "Detailed results",
"Results of the election:": "Results of the vote:",
"Unknown error. Try again please.": "Unknown error. Please try again.",
"If you list voters' emails, only them will be able to access the election": "If you list participants' emails, only they will be able to access the election",
"Voters received a link to vote by email. Each link can be used only once!": "Participants have received a link to vote by email. Each link can be used only once.",
"Oops... The election is unknown.": "Oops... The vote is unknown.",
"The election is still going on. You can't access now to the results.": "The vote is on-going. You cannot access the results at this time.",
"No votes have been recorded yet. Come back later.": "No votes have been recorded yet. Please check in later.",
"The election has not started yet.": "The vote has not started yet.",
"The election is over. You can't vote anymore": "The vote is over. You can no longer participate",
"You need a token to vote in this election": "You need a valid token to participate in this vote",
"You seem to have already voted.": "You seem to have already voted.",
"The parameters of the election are incorrect.": "The parameters of the vote are incorrect.",
"Support us !": "Support us!",
"PayPal - The safer, easier way to pay online!": "PayPal - The safer, easier way to pay online!",
"resource.numVotes": "Number of votes:",
"Access to results": "Results availability",
"Immediately": "Immediately",
"At the end of the election": "At the end of the vote",
"Results available at the close of the vote": "Results available at the close of the vote",
"The results page will not be accessible until all participants have voted.": "The results page will not be accessible until all participants have voted.",
"The results page will not be accessible until the end date is reached.": "The results page will not be accessible until the end date is reached.",
"No one will be able to see the result until the end date is reached or until all participants have voted.": "No one will be able to see the results until the end date is reached or until all participants have voted.",
"Send me this link": "Send me this link",
"Send me these links": "Send me these links",
"Open": "Open",
"Voting address": "Link to the vote",
"Results address": "Link to the results",
"Share election on Facebook": "Share vote on Facebook",
"Share results on Facebook": "Share results on Facebook"
}

@ -1,108 +0,0 @@
{
"title": "Plataforma de Juicio Mayoritario",
"Homepage": "Página de inicio",
"Source code": "Código fuente",
"Who are we": "Quiénes somos",
"Privacy policy": "Política de privacidad",
"resource.legalNotices": "Avisos legales",
"FAQ": "FAQ",
"resource.help": "¿Necesitas ayuda?",
"BetterVote": "VotarMejor",
"Voting platform": "Plataforma de votación",
"Majority Judgment": "Juicio Mayoritario",
"Start an election": "Iniciar una elección",
"resource.candidatePlaceholder": "Nombre del(la) candidato(a)/propuesta...",
"Delete?": "Borrar?",
"Are you sure to delete": "Estás seguro de querer borrar",
"the row": "la fila",
"Write here your question or introduce simple your election (250 characters max.)": "Escriba aquí su pregunta o introduzca simplemente su elección (250 caracteres máx.)",
"Enter the name of your candidate or proposal here (250 characters max.)": "Escriba aquí el nombre de su candidato o propuesta (250 caracteres como máximo)",
"Please add at least 2 candidates.": "Por favor, añada al menos dos canidatos(as).",
"Question of the election": "Pregunta de su elección",
"Write here the question of your election": "Escriba aquí la pregunta de su elección",
"For example:": "Por ejemplo:",
"For the role of my representative, I judge this candidate...": "Para ser mi representante, yo elijo a este(a) candidato(a)....",
"Candidates/Proposals": "Candidatos(as)/Propuestas",
"Add a proposal": "Añadir una propuesta",
"Advanced options": "Opciones avanzadas",
"Starting date": "Fecha de inicio",
"Ending date": "Fecha de finalización",
"Defined period" : "Período definido",
"Unlimited" : "Ilimitado",
"Voting time" : "Hora de la votación",
"Grades": "Escala",
"You can select here the number of grades for your election": "Puede seleccionar aquí el número de niveles de la escala para su elección",
"5 = Excellent, Very good, Good, Fair, Passable": "5 == Excelente, Muy bien, Bien, Regular, Pasable",
"Participants:": "Participantes",
"Add here participants' emails": "Añadir aquí los correos electrónicos de los(as) participantes",
"List voters' emails in case the election is not opened": "Enumere los correos electrónicos de los(as) votantes en caso de que la elección no se abra",
"Validate": "Validar",
"Submit my vote": "Validar",
"Confirm your vote": "Confirme su voto",
"The form contains no address.": "El formulario no contiene ningún correo electrónico",
"The election will be opened to anyone with the link": "La elección se abrirá a cualquiera que tenga el enlace",
"Start the election": "Iniciar la elección",
"Cancel": "Cancelar",
"Confirm": "Confirmar",
"Successful election creation!": "La elección ha sido creada con éxito!",
"You can now share the election link to participants:": "Ahora puede compartir el enlace de la elección con los(as) participantes",
"Copy": "Copiar",
"Here is the link for the results in real time:": "En este enlace puedes revisar los resultados en tiempo real",
"Keep these links carefully": "Guarda cuidadosamente estos enlaces",
"resource.participateBtn": "¡Participa ahora!",
"t": "<0>Advertencia</0>: No tendrás otras opciones para recuperar los enlaces, y no podremos compartirlos contigo. Por ejemplo, puedes agregarlos a favoritos de tu buscador.",
"Simple and free: organize an election with Majority Judgment.": "Simple y gratuito: organiza una elección con Juicio Mayoritario",
"Start": "Comenzar",
"No advertising or ad cookies": "No contiene publicidad ni cookies publicitarias",
"Oops! This election does not exist or it is not available anymore.": "¡Uy! Esta elección no existe o ya no está disponible",
"You can start another election.": "Puedes empezar otra elección",
"Go back to homepage": "Vuelve a la página de inicio",
"You have to judge every candidate/proposal!": "¡Tienes que evaluar a todos(as) los(as) candidatos(as)/propuestas",
"resource.voteSuccess": "¡Su participación fue registrada con éxito!",
"resource.thanks": "Muchas gracias por participar",
"Ending date:": "Fecha de finalización:",
"Excellent": "Excelente",
"Very good": "Muy bien",
"Good": "Bien",
"Fair": "Regular",
"Passable": "Pasable",
"Insufficient": "Insuficiente",
"To reject": "Rechazar",
"Dates": "Fechas",
"The election will take place from": "La elección tendrá lugar desde",
"at": "a las",
"to": "hasta",
"Voters' list": "Lista de votantes",
"Graph": "Gráfico",
"Preference profile": "Perfil de preferencia",
"Results of the election:": "Resultados de la elección",
"PayPal - The safer, easier way to pay online!": "PayPal la forma más segura y fácil de pagar en linea!",
"Support us !": "¡apórtanos!",
"Who are we?": "¿Quiénes somos?",
"Unknown error. Try again please.": "Error desconocido. Inténtelo de nuevo, por favor.",
"If you list voters' emails, only them will be able to access the election": "Si enumera los correos electrónicos de los votantes, sólo ellos podrán acceder a la elección",
"Voters received a link to vote by email. Each link can be used only once!": "Los votantes recibieron un enlace para votar por correo electrónico. ¡Cada enlace puede ser usado sólo una vez!",
"resource.numVotes": "Número de votos:",
"Oops... The election is unknown.": "Oops... La elección es desconocida",
"The election is still going on. You can't access now to the results.": "La elección sigue en marcha. No puedes acceder ahora a los resultados.",
"No votes have been recorded yet. Come back later.": "Aún no se han registrado votos. Vuelva más tarde.",
"The election has not started yet.": "Las elecciones aún no han comenzado.",
"The election is over. You can't vote anymore": "La elección ha terminado. Ya no puedes votar.",
"You need a token to vote in this election": "Necesitas una ficha para votar en esta elección",
"You seem to have already voted.": "Parece que ya has votado.",
"The parameters of the election are incorrect.": "Los parámetros de la elección son incorrectos.",
"Access to results" : "Acceso a los resultados",
"Immediately": "Inmediatamente",
"At the end of the election": "Al final de la elección",
"Results available at the close of the vote": "Resultados disponibles al cierre de la votación",
"The results page will not be accessible until all participants have voted.":"La página de resultados no será accesible hasta que todos los participantes hayan votado.",
"The results page will not be accessible until the end date is reached.": "No se podrá acceder a la página de resultados hasta que se alcance la fecha de finalización.",
"No one will be able to see the result until the end date is reached or until all participants have voted.": "Nadie podrá ver el resultado hasta que se alcance la fecha final o hasta que todos los participantes hayan votado.",
"Send me this link" : "Envíame este enlace",
"Send me these links" : "Envíame estos enlaces",
"Open" : "Abrir",
"Voting address" : "URL de la votación",
"Results address" : "URL de los resultados",
"Share election on Facebook" : "Compartir la elección en Facebook",
"Share results on Facebook" : "Comparte los resultados en Facebook"
}

@ -1,8 +0,0 @@
{
"common.vote": "Votez !",
"common.mieuxvoter": "Mieux Voter",
"common.helpus": "Vous souhaitez nous soutenir ?",
"common.candidates": "Candidats/Propositions",
"common.valueProp": "Simple et gratuit : organisez un vote avec le Jugement Majoritaire",
"common.backHomepage": "Retour à la page d'accueil"
}

@ -1,10 +0,0 @@
{
"email.hello": "Bonjour ! 🙂",
"email.why": "Vous avez été invité·e à participer à l'élection suivante : ",
"email.linkVote": "Le lien pour voter est le suivant :",
"email.linkResult": "A la fin de l'élection, vous pourrez accéder aux résultats en cliquant sur ce lien :",
"email.happy": "Nous sommes très heureux de vous partager ce lien de vote ! Vous allez pouvoir voter avec le jugement majoritaire.",
"email.copyLink": "Si le lien ne fonctionne pas, vous pouvez le copier et le coller dans la barre de navigation de votre navigateur.",
"email.bye": "Bon vote ! 🤗",
"email.aboutjm": "If you require any further information, please visit our site."
}

@ -1,11 +0,0 @@
{
"error.e1": "Impossible de retrouver le vote en question",
"error.e2": "L'élection est encore en cours. Revenez plus tard.",
"error.e3": "Aucun vote n'a encore été enregistré. Revenez plus tard.",
"error.e4": "L'élection n'a pas encore démarrée.",
"error.e5": "L'élection est terminée. Vous ne pouvez plus voter.",
"error.e6": "Vous avez besoin d'un jeton pour participer à cette élection.",
"error.e7": "Vous avez déjà voté pour cette élection.",
"error.e8": "Les paramètres de l'élection sont inconnues.",
"error.catch22": "Erreur inconnue."
}

@ -1,53 +0,0 @@
{
"Homepage": "Accueil",
"Source code": "Code source",
"Who are we": "Qui sommes-nous",
"BetterVote": "MieuxVoter",
"Voting platform": "Plateforme de vote",
"Majority Judgment": "Jugement Majoritaire",
"Start an election": "Lancer une élection",
"resource.candidatePlaceholder": "Name du candidat/proposition",
"Delete?": "Supprimer ?",
"Are you sure to delete": "Êtes-vous sûr(e) de supprimer",
"the row": "la ligne",
"Write here your question or introduce simple your election (250 characters max.)": "Décrire ici votre question ou introduire simplement votre élection (250 caractères max.)",
"Please add at least 2 candidates.": "Merci d'ajouter au moins 2 candidats.",
"Question of the election": "Question de votre élection",
"Write here the question of your election": "Ecrire ici la question de votre élection",
"For example:": "Par exemple",
"For the role of my representative, I judge this candidate...": "Pour être mon représentant, je juge ce candidat...",
"Candidates/Proposals": "Candidats/Propositions",
"Add a proposal": "Ajouter une proposition",
"Advanced options": "Options avancées",
"Starting date:": "Date de début :",
"Ending date: ": "Date de fin : ",
"Grades:": "Mentions",
"You can select here the number of grades for your election": "You pouvez choisir ici le nombre de mentions de votre élection",
"5 = Excellent, Very good, Good, Fair, Passable": "5 = Excellent, Très bien, Bien, Assez bien, Passable",
"Participants:": "Participants :",
"Add here participants' emails": "Ajouter ici les emails des participants",
"List voters' emails in case the election is not opened": "Lister ici les emails des électeurs dans le cas où l'élection n'est pas ouverte.",
"Validate": "Valider",
"Confirm your vote": "Confirmer votre vote",
"The form contains no address.": "Aucune adresse email n'a été ajoutée.",
"The election will be opened to anyone with the link": "L'élection sera accessible à tous ceux qui disposent de ce lien",
"Start the election": "Démarrer l'élection",
"Cancel": "Annuler",
"Confirm": "Valider",
"Successful election creation!": "L'élection a été créée avec succès !",
"You can now share the election link to participants:": "Vous pouvez maintenant partager ce lien à tous les participants",
"Copy": "Copier",
"Here is the link for the results in real time:": "Voici le lien pour afficher les résultats en temps réel :",
"Keep these links carefully": "Gardez ces liens précieusement",
"resource.participateBtn": "Participez maintenant !",
"t": "<0>Attention</0> : vous n'aurez pas d'autres moyens pour récupérer ces liens par la suite, et nous ne serons pas capables de les partager avec vous. Vous pouvez, par exemple, ajouter ces liens à vos favoris dans votre navigateur.",
"Simple and free: organize an election with Majority Judgment.": "Simple et grauit: organiser une élection avec le Jugement Majoritaire.",
"Start": "Démarrer",
"No advertising or ad cookies": "Pas de publictés, ni de cookies publicitaires",
"Oops! This election does not exist or it is not available anymore.": "Oups ! L'élection n'existe pas ou n'est plus disponible.",
"You can start another election.": "Vous pouvez démarrer une autre élection.",
"Go back to homepage": "Revenir à la page d'accueil",
"You have to judge every candidate/proposal!": "Vous devez évaluer tous les candidats/propositions !",
"resource.voteSuccess": "Votre participation a été enregistrée avec succès !",
"resource.thanks": "Merci de votre participation."
}

@ -1,105 +0,0 @@
{
"title": "Application au Jugement Majoritaire",
"Homepage": "Accueil",
"Source code": "Code source",
"Who are we?": "Qui sommes-nous ?",
"Privacy policy": "Politique de confidentialité",
"resource.legalNotices": "Mentions légales",
"FAQ": "FAQ",
"resource.help": "Besoin d'aide ?",
"BetterVote": "MieuxVoter",
"Voting platform": "Plateforme de vote",
"Majority Judgment": "Jugement Majoritaire",
"Start an election": "Lancer un vote",
"resource.candidatePlaceholder": "Nom du candidat/proposition",
"Delete?": "Supprimer ?",
"Are you sure to delete": "Êtes-vous sûr(e) de supprimer",
"the row": "la ligne",
"resource.writeQuestionHere": "Décrire ici votre question ou introduire simplement votre vote (250 caractères max.)",
"Enter the name of your candidate or proposal here (250 characters max.)": "Saisissez ici le nom de votre candidat ou de votre proposition (250 caractères max.)",
"Please add at least 2 candidates.": "Merci d'ajouter au moins 2 candidats.",
"resource.questionLabel": "Question de votre vote",
"resource.writeQuestion": "Ecrire ici la question de votre vote",
"resource.eg": "Par exemple",
"resource.exampleQuestion": "Pour être mon représentant, je juge ce candidat...",
"Add a proposal": "Ajouter une proposition",
"resource.advancedOptions": "Options avancées",
"Starting date": "Date de début",
"Ending date": "Date de fin ",
"Defined period": "Période définie",
"Unlimited": "Illimitée",
"Voting time": "Durée du vote",
"Grades": "Mentions",
"You can select here the number of grades for your election": "You pouvez choisir ici le nombre de mentions de votre vote",
"5 = Excellent, Very good, Good, Fair, Passable": "5 = Excellent, Très bien, Bien, Assez bien, Passable",
"Participants": "Participants",
"Add here participants' emails": "Ajouter ici les emails des participants",
"List voters' emails in case the election is not opened": "Lister ici les emails des électeurs dans le cas où le vote n'est pas ouverte.",
"Validate": "Valider",
"Submit my vote": "Enregistrer mon vote",
"Confirm your vote": "Confirmer votre vote",
"The form contains no address.": "Aucune adresse email n'a été ajoutée.",
"The election will be opened to anyone with the link": "Le vote sera accessible à tous ceux qui disposent du lien",
"resource.startVote": "Démarrer le vote",
"Cancel": "Annuler",
"Confirm": "Valider",
"Successful election creation!": "Le vote a été créé avec succès !",
"You can now share the election link to participants:": "Vous pouvez maintenant partager ce lien à tous les participants",
"Copy": "Copier",
"Here is the link for the results in real time:": "Voici le lien pour afficher les résultats en temps réel :",
"Keep these links carefully": "Gardez ces liens précieusement",
"resource.participateBtn": "Participez maintenant !",
"resource.resultsBtn": "Résultats",
"t": "<0>Attention</0> : vous n'aurez pas d'autres moyens pour récupérer ces liens par la suite, et nous ne serons pas capables de les partager avec vous. Vous pouvez, par exemple, ajouter ces liens à vos favoris dans votre navigateur.",
"resource.start": "Démarrer",
"resource.noAds": "Pas de publicités, ni de cookies publicitaires",
"Oops! This election does not exist or it is not available anymore.": "Oups ! Le vote n'existe pas ou n'est plus disponible.",
"You can start another election.": "Vous pouvez démarrer une autre vote.",
"Go back to homepage": "Revenir à la page d'accueil",
"You have to judge every candidate/proposal!": "Vous devez évaluer tous les candidats/propositions !",
"resource.voteSuccess": "Votre participation a été enregistrée avec succès !",
"resource.thanks": "Merci de votre participation.",
"Excellent": "Excellent",
"Very good": "Très bien",
"Good": "Bien",
"Fair": "Assez bien",
"Passable": "Passable",
"Insufficient": "Insuffisant",
"To reject": "A rejeter",
"Dates": "Dates",
"The election will take place from": "Le vote se déroulera du",
"at": "à",
"to": "au",
"Voters' list": "Listes des électeurs",
"Graph": "Graphique",
"Preference profile": "Profil de mérites",
"Results of the election:": "Résultats du vote",
"Unknown error. Try again please.": "Erreur inconnue. Merci de ré-essayer plus tard.",
"If you list voters' emails, only them will be able to access the election": "Si vous ajoutez des emails, seulement ceux-là seront capables d'accéder au vote",
"Voters received a link to vote by email. Each link can be used only once!": "Les électeurs ont reçu un lien par courriel pour voter. Chaque lien ne peut être utilisé qu'une seule fois.",
"Oops... The election is unknown.": "Oups... Le serveur ne retrouve pas le vote.",
"The election is still going on. You can't access now to the results.": "le vote est encore en cours. Vous ne pouvez pas encore accéder aux résultats.",
"No votes have been recorded yet. Come back later.": "Aucun vote n'a été enregistré. Merci de revenir plus tard.",
"The election has not started yet.": "le vote n'a pas encore commencé.",
"The election is over. You can't vote anymore": "le vote est terminée. Vous ne pouvez plus voter.",
"You need a token to vote in this election": "Vous avez besoin d'un jeton pour participer à ce vote",
"You seem to have already voted.": "Il semble que vous ayez déjà voté.",
"The parameters of the election are incorrect.": "Les paramètres de vote sont incorrects.",
"Support us !": "Soutenez-nous !",
"PayPal - The safer, easier way to pay online!": "PayPal - Le moyen le plus sûr et le plus simple de payer en ligne !",
"resource.numVotes": "Nombre de votes :",
"Access to results": "Accès aux résultats",
"Immediately": "Immédiatement",
"At the end of the election": "A la clôture du vote",
"Results available at the close of the vote": "Résultats disponibles à la clôture du vote",
"The results page will not be accessible until all participants have voted.": "La page de résultats ne sera pas accessible tant que tous les participants n'auront pas voté.",
"The results page will not be accessible until the end date is reached.": "La page de résultats ne sera pas accessible tant que la date de fin ne sera pas atteinte.",
"No one will be able to see the result until the end date is reached or until all participants have voted.": "Personne ne pourra voir le résultat tant que la date de fin n'est pas atteinte ou que tous les participants n'ont pas voté.",
"Send me this link": "Envoyez-moi ce lien",
"Send me these links": "Envoyez-moi ces liens",
"Open": "Ouvrir",
"Voting address": "Adresse du vote",
"Results address": "Adresse des résultats",
"Share election on Facebook": "Partager le vote sur Facebook",
"Share results on Facebook": "Partager ces résultats sur Facebook"
}

@ -1,107 +0,0 @@
{
"title": "Решение Большинства",
"Homepage": "Главная страница",
"Source code": "Исходный код",
"Who are we?": "Кто мы?",
"Privacy policy": "Политика конфиденциальности",
"resource.legalNotices": "Официальные уведомления",
"FAQ": "Часто задаваемые вопросы",
"resource.help": "Нужна помощь?",
"BetterVote": "BetterVote",
"Voting platform": "Платформа голосования",
"Majority Judgment": "Решение Большинства",
"Start an election": "Создать голосование",
"resource.candidatePlaceholder": "Имя кандидата/предлжения...",
"Delete?": "Удалить?",
"Are you sure to delete": "Вы уверены в удалении",
"the row": "ряд",
"Write here your question or introduce simple your election (250 characters max.)": "Напишите свой вопрос или опишите голосование (250 символов максимум.)",
"Enter the name of your candidate or proposal here (250 characters max.)": "Введите имя вашего кандидата или предложение здесь (не более 250 символов).",
"Please add at least 2 candidates.": "Пожалуйста добавьте как минимум 2 кандидатов",
"Question of the election": "Суть голосования",
"Write here the question of your election": "Напишите вопрос вашего голосования",
"For example:": "Например:",
"For the role of my representative, I judge this candidate...": "На роль моего представителя, я считаю этого кандидата...",
"Candidates/Proposals": "Кандидаты/Предложения",
"Add a proposal": "Добавьте предложение",
"Advanced options": "Расширенные настройки",
"Starting date": "Дата начала",
"Ending date": "Дата окончания",
"Defined period" : "Определенный период",
"Unlimited" : "Безлимитный",
"Voting time" : "Время голосования",
"Grades": "Оценки",
"You can select here the number of grades for your election": "Здесь вы можете выбрать количество оценок для вашего голосования",
"5 = Excellent, Very good, Good, Fair, Passable": "5 = Отлично, Очень хорошо, Хорошо, Удовлетворительно, Допустимо",
"Participants:": "Участники:",
"Add here participants' emails": "Добавьте электронную почту участников",
"List voters' emails in case the election is not opened": "Укажите электронные адреса голосующих на случай, если голосование не откроется",
"Validate": "Подтвердить",
"Submit my vote": "Подтвердить",
"Confirm your vote": "Подтвердите свой голос",
"The form contains no address.": "Адрес не указан.",
"The election will be opened to anyone with the link": "Голосование будет доступно любому, у кого есть ссылка",
"Start the election": "Начать голосование",
"Cancel": "Отменить",
"Confirm": "Подтвердить",
"Successful election creation!": "Голосование создано!",
"You can now share the election link to participants:": "Теперь вы можете поделить ссылкой с участниками:",
"Copy": "Скопировать",
"Here is the link for the results in real time:": "Ссылка на результаты в режиме онлайн:",
"Keep these links carefully": "Сохраните эти ссылки, чтобы не потерять",
"resource.participateBtn": "Участвуйте сейчас!",
"t": "<0>Предупреждение</0>: вы не сможете восстановить ссылки и не сможете ими поделиться. Например, вы можете добавить их в избранное в вашем браузере.",
"Simple and free: organize an election with Majority Judgment.": "Просто и бесплатно: создайте голосование с Решением Большинства.",
"Start": "Начать",
"No advertising or ad cookies": "Никакой рекламы",
"Oops! This election does not exist or it is not available anymore.": "Упс! Это голосование не существует или больше не доступно.",
"You can start another election.": "Вы можете создать другое голосование.",
"Go back to homepage": "Вернуться на главную",
"You have to judge every candidate/proposal!": "Вам нужно проголосовать за каждого кандидата/предложение!",
"resource.voteSuccess": "Ваш голос был учтен!",
"resource.thanks": "Спасибо за ваше участие.",
"Ending date:": "Дата окончания:",
"Excellent": "Отлично",
"Very good": "Очень хорошо",
"Good": "Хорошо",
"Fair": "Удовлетворительно",
"Passable": "Допустимо",
"Insufficient": "Неудовлетворительно",
"To reject": "Отклонять",
"Dates": "Даты",
"The election will take place from": "Голосование состоится",
"at": "в",
"to": "к",
"Voters' list": "Список проголосовавших",
"Graph": "Кривая",
"Preference profile": "Профиль заслуг",
"Results of the election:": "Результаты голосования:",
"Unknown error. Try again please.": "Неизвестная ошикба. Пожалуйста, попробуйте позже.",
"If you list voters' emails, only them will be able to access the election": "Если вы укажете электронные адреса участников, голосовать смогут только они",
"Voters received a link to vote by email. Each link can be used only once!": "Участники голосования получили ссылки на указанные адреса. Каждая ссылка может быть использована только один раз!",
"Oops... The election is unknown.": "Упс... Неизвестное голосование.",
"The election is still going on. You can't access now to the results.": "Голосование все еще в процессе. Вы не можете увидеть результаты сейчас.",
"No votes have been recorded yet. Come back later.": "Ни одного голоса не было записано. Пожалуйста, вернитесь позже.",
"The election has not started yet.": "Голосование еще не началось.",
"The election is over. You can't vote anymore": "Голосвание закончено. Вы больше не можете проголосовать.",
"You need a token to vote in this election": "Для участия вам необходим жетон",
"You seem to have already voted.": "Кажется, вы уже проголосовали.",
"The parameters of the election are incorrect.": "Параметры голосвания неверны.",
"Support us !": "Поддержите нас !",
"PayPal - The safer, easier way to pay online!": "PayPal - Безопасный и простой способ платить онлайн!",
"resource.numVotes": "Количество голосов:",
"Access to results" : "Доступ к результатам",
"Immediately": "Немедленно",
"At the end of the election": "По окончании выборов",
"Results available at the close of the vote": "Результаты, доступные по окончании голосования",
"The results page will not be accessible until all participants have voted.":"Страница результатов не будет доступна до тех пор, пока все участники не проголосуют.",
"The results page will not be accessible until the end date is reached.": "Страница результатов не будет доступна до тех пор, пока не будет достигнута конечная дата.",
"No one will be able to see the result until the end date is reached or until all participants have voted.": "Никто не сможет увидеть результат до тех пор, пока не будет достигнут конечный срок или пока все участники не проголосуют.",
"Send me this link" : "Пришлите мне эту ссылку",
"Send me these links" : "Пришлите мне эти ссылки",
"Open" : "Открыть",
"Voting address" : "URL-адрес для голосования",
"Results address" : "URL-адрес результатов",
"Share election on Facebook" : "Поделиться выборами на Facebook",
"Share results on Facebook" : "Поделиться результатами на Facebook"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="LOGO" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve">
<style type="text/css">
.st0{fill:#000000;}
</style>
<path class="st0" d="M200,0C89.54,0,0,89.54,0,200s89.54,200,200,200s200-89.54,200-200S310.46,0,200,0z M249.67,210.48
c-11.87,32.86-23.84,65.69-35.55,98.6c-1.4,3.94-3.16,5.48-7.41,5.31c-8.48-0.35-16.99-0.22-25.48-0.04
c-3.12,0.07-4.74-0.87-5.85-3.94c-8.77-24.43-17.67-48.81-26.65-73.16c-1.07-2.91-1.05-4.42,2.36-5.45
c6.85-2.06,13.63-4.38,20.29-6.97c3.22-1.25,4.29-0.33,5.26,2.7c5.26,16.48,10.7,32.91,16.08,49.35c0.5,1.54,1.06,3.06,1.64,4.71
c1.99-1.26,2-3.28,2.56-4.93c11.12-32.77,22.25-65.54,33.17-98.37c1.34-4.04,3-5.88,7.6-5.63c8.26,0.45,16.57,0.13,25.52,0.13
C258.54,185.81,254.12,198.15,249.67,210.48z M263.37,154.73c0.01,2.52-0.65,3.54-3.34,3.48c-7.66-0.15-15.33-0.19-22.99,0.01
c-3.09,0.08-3.52-1.28-3.48-3.87c0.12-7.45,0.04-14.9,0.04-22.59c-2.19,1.2-1.94,3.31-2.43,4.91c-4.76,15.41-9.49,30.83-13.94,46.32
c-0.96,3.36-2.37,4.44-5.83,4.32c-7.65-0.28-15.33-0.3-22.99,0.01c-3.58,0.14-4.78-1.21-5.72-4.45
c-4.73-16.28-9.75-32.48-14.68-48.71c-0.23-0.76-0.55-1.49-1.67-2.2c0,1.51,0,3.03,0,4.54c0,18-0.08,35.99,0.07,53.98
c0.03,3.35-0.68,4.69-4.34,4.55c-7.15-0.29-14.33-0.22-21.49-0.02c-2.96,0.08-4.04-0.63-4.03-3.83c0.11-33.16,0.11-66.31,0-99.47
c-0.01-3.38,1.15-4.16,4.29-4.11c10.99,0.16,22,0.19,32.99-0.01c3.26-0.06,4.56,1.07,5.56,4.13c6.06,18.66,12.35,37.24,18.59,55.84
c0.5,1.5,1.14,2.96,2.17,5.6c3.53-10.55,6.67-19.91,9.8-29.28c3.63-10.88,7.36-21.73,10.82-32.67c0.86-2.71,2.07-3.64,4.9-3.61
c11.33,0.15,22.66,0.14,33.99,0.01c2.78-0.03,3.75,0.77,3.73,3.64C263.28,112.41,263.29,133.57,263.37,154.73z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="LOGO" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve">
<style type="text/css">
.st0{fill:#2A43A0;}
</style>
<path class="st0" d="M200,0C89.54,0,0,89.54,0,200s89.54,200,200,200s200-89.54,200-200S310.46,0,200,0z M249.67,210.48
c-11.87,32.86-23.84,65.69-35.55,98.6c-1.4,3.94-3.16,5.48-7.41,5.31c-8.48-0.35-16.99-0.22-25.48-0.04
c-3.12,0.07-4.74-0.87-5.85-3.94c-8.77-24.43-17.67-48.81-26.65-73.16c-1.07-2.91-1.05-4.42,2.36-5.45
c6.85-2.06,13.63-4.38,20.29-6.97c3.22-1.25,4.29-0.33,5.26,2.7c5.26,16.48,10.7,32.91,16.08,49.35c0.5,1.54,1.06,3.06,1.64,4.71
c1.99-1.26,2-3.28,2.56-4.93c11.12-32.77,22.25-65.54,33.17-98.37c1.34-4.04,3-5.88,7.6-5.63c8.26,0.45,16.57,0.13,25.52,0.13
C258.54,185.81,254.12,198.15,249.67,210.48z M263.37,154.73c0.01,2.52-0.65,3.54-3.34,3.48c-7.66-0.15-15.33-0.19-22.99,0.01
c-3.09,0.08-3.52-1.28-3.48-3.87c0.12-7.45,0.04-14.9,0.04-22.59c-2.19,1.2-1.94,3.31-2.43,4.91c-4.76,15.41-9.49,30.83-13.94,46.32
c-0.96,3.36-2.37,4.44-5.83,4.32c-7.65-0.28-15.33-0.3-22.99,0.01c-3.58,0.14-4.78-1.21-5.72-4.45
c-4.73-16.28-9.75-32.48-14.68-48.71c-0.23-0.76-0.55-1.49-1.67-2.2c0,1.51,0,3.03,0,4.54c0,18-0.08,35.99,0.07,53.98
c0.03,3.35-0.68,4.69-4.34,4.55c-7.15-0.29-14.33-0.22-21.49-0.02c-2.96,0.08-4.04-0.63-4.03-3.83c0.11-33.16,0.11-66.31,0-99.47
c-0.01-3.38,1.15-4.16,4.29-4.11c10.99,0.16,22,0.19,32.99-0.01c3.26-0.06,4.56,1.07,5.56,4.13c6.06,18.66,12.35,37.24,18.59,55.84
c0.5,1.5,1.14,2.96,2.17,5.6c3.53-10.55,6.67-19.91,9.8-29.28c3.63-10.88,7.36-21.73,10.82-32.67c0.86-2.71,2.07-3.64,4.9-3.61
c11.33,0.15,22.66,0.14,33.99,0.01c2.78-0.03,3.75,0.77,3.73,3.64C263.28,112.41,263.29,133.57,263.37,154.73z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="LOGO" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve">
<style type="text/css">
.st0{fill:#2A43A0;}
.st1{fill:#EE455B;}
.st2{fill:#FDFDFE;}
</style>
<circle id="BACKGROUND" class="st0" cx="200" cy="200" r="200"/>
<path id="V" class="st1" d="M194.36,281.6c1.99-1.26,2-3.28,2.56-4.93c11.12-32.77,22.25-65.54,33.17-98.37
c1.34-4.04,3-5.88,7.6-5.63c8.26,0.45,16.57,0.13,25.52,0.13c-4.67,13.01-9.09,25.35-13.54,37.68
c-11.87,32.86-23.84,65.69-35.55,98.6c-1.4,3.94-3.16,5.48-7.41,5.31c-8.48-0.35-16.99-0.22-25.48-0.04
c-3.12,0.07-4.74-0.87-5.85-3.94c-8.77-24.43-17.67-48.81-26.65-73.16c-1.07-2.91-1.05-4.42,2.36-5.45
c6.85-2.06,13.63-4.38,20.29-6.97c3.22-1.25,4.29-0.33,5.26,2.7c5.26,16.48,10.7,32.91,16.08,49.35
C193.22,278.43,193.78,279.95,194.36,281.6z"/>
<path id="M" class="st2" d="M200.14,153.15c3.53-10.55,6.67-19.91,9.8-29.28c3.63-10.88,7.36-21.73,10.82-32.67
c0.86-2.71,2.07-3.64,4.9-3.61c11.33,0.15,22.66,0.14,33.99,0.01c2.78-0.03,3.75,0.77,3.73,3.64c-0.1,21.16-0.09,42.32-0.01,63.48
c0.01,2.52-0.65,3.54-3.34,3.48c-7.66-0.15-15.33-0.19-22.99,0.01c-3.09,0.08-3.52-1.28-3.48-3.87c0.12-7.45,0.04-14.9,0.04-22.59
c-2.19,1.2-1.94,3.31-2.43,4.91c-4.76,15.41-9.49,30.83-13.94,46.32c-0.96,3.36-2.37,4.44-5.83,4.32
c-7.65-0.28-15.33-0.3-22.99,0.01c-3.58,0.14-4.78-1.21-5.72-4.45c-4.73-16.28-9.75-32.48-14.68-48.71
c-0.23-0.76-0.55-1.49-1.67-2.2c0,1.51,0,3.03,0,4.54c0,18-0.08,35.99,0.07,53.98c0.03,3.35-0.68,4.69-4.34,4.55
c-7.15-0.29-14.33-0.22-21.49-0.02c-2.96,0.08-4.04-0.63-4.03-3.83c0.11-33.16,0.11-66.31,0-99.47c-0.01-3.38,1.15-4.16,4.29-4.11
c10.99,0.16,22,0.19,32.99-0.01c3.26-0.06,4.56,1.07,5.56,4.13c6.06,18.66,12.35,37.24,18.59,55.84
C198.48,149.05,199.11,150.5,200.14,153.15z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="LOGO" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve">
<style type="text/css">
.st0{fill:#000000;}
</style>
<path class="st0" d="M200,8c25.92,0,51.07,5.08,74.73,15.09c22.86,9.67,43.4,23.51,61.03,41.15c17.64,17.64,31.48,38.17,41.15,61.03
C386.92,148.93,392,174.08,392,200s-5.08,51.07-15.09,74.73c-9.67,22.86-23.51,43.4-41.15,61.03
c-17.64,17.64-38.17,31.48-61.03,41.15C251.07,386.92,225.92,392,200,392s-51.07-5.08-74.73-15.09
c-22.86-9.67-43.4-23.51-61.03-41.15c-17.64-17.64-31.48-38.17-41.15-61.03C13.08,251.07,8,225.92,8,200s5.08-51.07,15.08-74.73
c9.67-22.86,23.51-43.4,41.15-61.03c17.64-17.64,38.17-31.48,61.03-41.15C148.93,13.08,174.08,8,200,8 M157.57,79.72
c-5.34,0-10.77-0.04-16.61-0.13c-0.13,0-0.25,0-0.37,0c-1.39,0-5.63,0-8.83,3.21c-3.23,3.24-3.21,7.53-3.21,8.93
c0.11,32.12,0.12,65.57,0,99.42c0,1.37-0.02,5.55,3.15,8.73c3.12,3.13,7.15,3.13,8.47,3.13c0.21,0,0.42,0,0.63-0.01
c4.16-0.11,7.78-0.17,11.09-0.17c3.53,0,6.76,0.06,9.86,0.19c0.29,0.01,0.57,0.02,0.84,0.02c3.59,0,6.49-1.08,8.61-3.22
c3.27-3.29,3.23-7.73,3.22-9.4c-0.02-2.5-0.04-5.01-0.05-7.53c0.22,0.74,0.43,1.48,0.65,2.22c1.97,6.79,6.32,10.23,12.91,10.23
c0.26,0,0.54-0.01,0.82-0.02c3.58-0.14,7.29-0.22,11.03-0.22c3.68,0,7.5,0.07,11.35,0.21c0.27,0.01,0.54,0.01,0.8,0.01
c1.34,0,2.79-0.15,4.24-0.56c-7.07,21.09-14.23,42.25-21.28,63.04c-3.54-10.78-7.15-21.8-10.62-32.69
c-2.45-7.67-7.68-8.82-10.58-8.82c-1.62,0-3.32,0.35-5.2,1.08c-6.4,2.48-13.03,4.76-19.71,6.77c-3.66,1.1-6.22,3.07-7.61,5.86
c-2.1,4.2-0.59,8.28,0.05,10.02c9.7,26.29,18.41,50.2,26.63,73.09c2.17,6.04,6.69,9.24,13.07,9.24c0.16,0,0.31,0,0.47-0.01
c4.28-0.09,8.89-0.18,13.54-0.18c4.19,0,7.93,0.07,11.45,0.21c0.32,0.01,0.64,0.02,0.94,0.02c10.55,0,13.4-8.01,14.33-10.64
c8.58-24.11,17.45-48.58,26.02-72.25c3.18-8.77,6.35-17.54,9.52-26.31c3.04-8.42,6.07-16.85,9.17-25.5c1.44-4.01,2.9-8.07,4.38-12.2
c0.88-2.45,0.51-5.18-0.99-7.31c-1.05-1.49-2.55-2.55-4.25-3.06c1.05-0.49,1.98-1.15,2.81-1.98c3.08-3.09,3.07-7.13,3.06-8.46
c-0.08-23.52-0.08-44.26,0.01-63.42c0.01-1.34,0.03-5.41-3.11-8.55c-3.12-3.13-7.15-3.13-8.47-3.13l-0.24,0
c-5.84,0.07-11.56,0.1-17,0.1c-5.74,0-11.38-0.04-16.79-0.11l-0.27,0c-4.32,0-9.97,1.6-12.37,9.2c-2.55,8.06-5.3,16.25-7.97,24.17
c-0.94,2.79-1.88,5.58-2.81,8.38c-0.78,2.35-1.57,4.7-2.36,7.07c-4.33-12.9-8.75-26.12-12.99-39.17c-2.08-6.41-6.43-9.66-12.93-9.66
c-0.12,0-0.25,0-0.38,0C168.53,79.67,163.11,79.72,157.57,79.72 M230.91,164.86c0.33,0.18,0.68,0.35,1.03,0.49
c-0.46,0.14-0.91,0.31-1.33,0.49C230.71,165.51,230.81,165.19,230.91,164.86 M200,0C89.54,0,0,89.54,0,200s89.54,200,200,200
s200-89.54,200-200S310.46,0,200,0L200,0z M157.57,87.72c5.42,0,10.84-0.04,16.26-0.14c0.08,0,0.16,0,0.24,0
c3.08,0,4.35,1.14,5.32,4.13c6.06,18.66,12.35,37.24,18.59,55.84c0.5,1.5,1.14,2.96,2.17,5.6c3.53-10.55,6.67-19.91,9.8-29.28
c3.63-10.88,7.36-21.73,10.82-32.67c0.84-2.66,2.02-3.61,4.74-3.61c0.05,0,0.11,0,0.16,0c5.63,0.08,11.26,0.11,16.9,0.11
c5.7,0,11.4-0.04,17.09-0.1c0.05,0,0.1,0,0.15,0c2.66,0,3.59,0.82,3.58,3.65c-0.1,21.16-0.09,42.32-0.01,63.48
c0.01,2.45-0.61,3.48-3.15,3.48c-0.06,0-0.13,0-0.2,0c-3.95-0.08-7.91-0.13-11.86-0.13c-3.71,0-7.42,0.04-11.13,0.14
c-0.09,0-0.17,0-0.26,0c-2.86,0-3.26-1.36-3.22-3.87c0.12-7.45,0.04-14.9,0.04-22.59c-2.19,1.2-1.94,3.31-2.43,4.91
c-4.76,15.41-9.49,30.83-13.94,46.32c-0.91,3.19-2.23,4.33-5.32,4.33c-0.16,0-0.33,0-0.51-0.01c-3.87-0.14-7.76-0.22-11.64-0.22
c-3.79,0-7.57,0.07-11.35,0.22c-0.17,0.01-0.33,0.01-0.49,0.01c-3.2,0-4.33-1.38-5.22-4.47c-4.73-16.28-9.75-32.48-14.68-48.71
c-0.23-0.76-0.55-1.49-1.67-2.2c0,1.51,0,3.03,0,4.54c0,17.99-0.08,35.99,0.07,53.98c0.03,3.18-0.62,4.56-3.82,4.56
c-0.17,0-0.34,0-0.52-0.01c-3.39-0.14-6.79-0.19-10.18-0.19c-3.77,0-7.54,0.07-11.3,0.17c-0.14,0-0.28,0.01-0.42,0.01
c-2.65,0-3.63-0.79-3.62-3.83c0.11-33.16,0.11-66.32,0-99.47c-0.01-3.29,1.09-4.11,4.04-4.11c0.08,0,0.17,0,0.25,0
C146.42,87.67,152,87.72,157.57,87.72L157.57,87.72z M194.36,281.6c1.99-1.26,2-3.28,2.56-4.93c11.12-32.77,22.25-65.54,33.17-98.37
c1.26-3.8,2.8-5.65,6.8-5.65c0.26,0,0.52,0.01,0.8,0.02c3.39,0.18,6.79,0.24,10.24,0.24c4.95,0,10-0.11,15.28-0.11
c-4.67,13.01-9.09,25.35-13.54,37.68c-11.87,32.86-23.84,65.69-35.55,98.6c-1.33,3.74-2.98,5.32-6.79,5.32c-0.2,0-0.41,0-0.62-0.01
c-3.92-0.16-7.85-0.22-11.78-0.22c-4.57,0-9.14,0.08-13.71,0.18c-0.1,0-0.2,0-0.3,0c-2.92,0-4.48-0.97-5.54-3.94
c-8.77-24.43-17.67-48.81-26.65-73.16c-1.07-2.91-1.05-4.42,2.36-5.45c6.85-2.06,13.63-4.38,20.29-6.97
c0.93-0.36,1.68-0.54,2.31-0.54c1.53,0,2.27,1.09,2.96,3.25c5.26,16.48,10.7,32.91,16.08,49.35
C193.22,278.43,193.78,279.95,194.36,281.6L194.36,281.6z"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="LOGO" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve">
<style type="text/css">
.st0{fill:#2943A0;}
</style>
<path class="st0" d="M200,8c25.92,0,51.07,5.08,74.73,15.09c22.86,9.67,43.4,23.51,61.03,41.15c17.64,17.64,31.48,38.17,41.15,61.03
C386.92,148.93,392,174.08,392,200s-5.08,51.07-15.09,74.73c-9.67,22.86-23.51,43.4-41.15,61.03
c-17.64,17.64-38.17,31.48-61.03,41.15C251.07,386.92,225.92,392,200,392s-51.07-5.08-74.73-15.09
c-22.86-9.67-43.4-23.51-61.03-41.15c-17.64-17.64-31.48-38.17-41.15-61.03C13.08,251.07,8,225.92,8,200s5.08-51.07,15.08-74.73
c9.67-22.86,23.51-43.4,41.15-61.03c17.64-17.64,38.17-31.48,61.03-41.15C148.93,13.08,174.08,8,200,8 M157.57,79.72
c-5.34,0-10.77-0.04-16.61-0.13c-0.13,0-0.25,0-0.37,0c-1.39,0-5.63,0-8.83,3.21c-3.23,3.24-3.21,7.53-3.21,8.93
c0.11,32.12,0.12,65.57,0,99.42c0,1.37-0.02,5.55,3.15,8.73c3.12,3.13,7.15,3.13,8.47,3.13c0.21,0,0.42,0,0.63-0.01
c4.16-0.11,7.78-0.17,11.09-0.17c3.53,0,6.76,0.06,9.86,0.19c0.29,0.01,0.57,0.02,0.84,0.02c3.59,0,6.49-1.08,8.61-3.22
c3.27-3.29,3.23-7.73,3.22-9.4c-0.02-2.5-0.04-5.01-0.05-7.53c0.22,0.74,0.43,1.48,0.65,2.22c1.97,6.79,6.32,10.23,12.91,10.23
c0.26,0,0.54-0.01,0.82-0.02c3.58-0.14,7.29-0.22,11.03-0.22c3.68,0,7.5,0.07,11.35,0.21c0.27,0.01,0.54,0.01,0.8,0.01
c1.34,0,2.79-0.15,4.24-0.56c-7.07,21.09-14.23,42.25-21.28,63.04c-3.54-10.78-7.15-21.8-10.62-32.69
c-2.45-7.67-7.68-8.82-10.58-8.82c-1.62,0-3.32,0.35-5.2,1.08c-6.4,2.48-13.03,4.76-19.71,6.77c-3.66,1.1-6.22,3.07-7.61,5.86
c-2.1,4.2-0.59,8.28,0.05,10.02c9.7,26.29,18.41,50.2,26.63,73.09c2.17,6.04,6.69,9.24,13.07,9.24c0.16,0,0.31,0,0.47-0.01
c4.28-0.09,8.89-0.18,13.54-0.18c4.19,0,7.93,0.07,11.45,0.21c0.32,0.01,0.64,0.02,0.94,0.02c10.55,0,13.4-8.01,14.33-10.64
c8.58-24.11,17.45-48.58,26.02-72.25c3.18-8.77,6.35-17.54,9.52-26.31c3.04-8.42,6.07-16.85,9.17-25.5c1.44-4.01,2.9-8.07,4.38-12.2
c0.88-2.45,0.51-5.18-0.99-7.31c-1.05-1.49-2.55-2.55-4.25-3.06c1.05-0.49,1.98-1.15,2.81-1.98c3.08-3.09,3.07-7.13,3.06-8.46
c-0.08-23.52-0.08-44.26,0.01-63.42c0.01-1.34,0.03-5.41-3.11-8.55c-3.12-3.13-7.15-3.13-8.47-3.13l-0.24,0
c-5.84,0.07-11.56,0.1-17,0.1c-5.74,0-11.38-0.04-16.79-0.11l-0.27,0c-4.32,0-9.97,1.6-12.37,9.2c-2.55,8.06-5.3,16.25-7.97,24.17
c-0.94,2.79-1.88,5.58-2.81,8.38c-0.78,2.35-1.57,4.7-2.36,7.07c-4.33-12.9-8.75-26.12-12.99-39.17c-2.08-6.41-6.43-9.66-12.93-9.66
c-0.12,0-0.25,0-0.38,0C168.53,79.67,163.11,79.72,157.57,79.72 M230.91,164.86c0.33,0.18,0.68,0.35,1.03,0.49
c-0.46,0.14-0.91,0.31-1.33,0.49C230.71,165.51,230.81,165.19,230.91,164.86 M200,0C89.54,0,0,89.54,0,200s89.54,200,200,200
s200-89.54,200-200S310.46,0,200,0L200,0z M157.57,87.72c5.42,0,10.84-0.04,16.26-0.14c0.08,0,0.16,0,0.24,0
c3.08,0,4.35,1.14,5.32,4.13c6.06,18.66,12.35,37.24,18.59,55.84c0.5,1.5,1.14,2.96,2.17,5.6c3.53-10.55,6.67-19.91,9.8-29.28
c3.63-10.88,7.36-21.73,10.82-32.67c0.84-2.66,2.02-3.61,4.74-3.61c0.05,0,0.11,0,0.16,0c5.63,0.08,11.26,0.11,16.9,0.11
c5.7,0,11.4-0.04,17.09-0.1c0.05,0,0.1,0,0.15,0c2.66,0,3.59,0.82,3.58,3.65c-0.1,21.16-0.09,42.32-0.01,63.48
c0.01,2.45-0.61,3.48-3.15,3.48c-0.06,0-0.13,0-0.2,0c-3.95-0.08-7.91-0.13-11.86-0.13c-3.71,0-7.42,0.04-11.13,0.14
c-0.09,0-0.17,0-0.26,0c-2.86,0-3.26-1.36-3.22-3.87c0.12-7.45,0.04-14.9,0.04-22.59c-2.19,1.2-1.94,3.31-2.43,4.91
c-4.76,15.41-9.49,30.83-13.94,46.32c-0.91,3.19-2.23,4.33-5.32,4.33c-0.16,0-0.33,0-0.51-0.01c-3.87-0.14-7.76-0.22-11.64-0.22
c-3.79,0-7.57,0.07-11.35,0.22c-0.17,0.01-0.33,0.01-0.49,0.01c-3.2,0-4.33-1.38-5.22-4.47c-4.73-16.28-9.75-32.48-14.68-48.71
c-0.23-0.76-0.55-1.49-1.67-2.2c0,1.51,0,3.03,0,4.54c0,17.99-0.08,35.99,0.07,53.98c0.03,3.18-0.62,4.56-3.82,4.56
c-0.17,0-0.34,0-0.52-0.01c-3.39-0.14-6.79-0.19-10.18-0.19c-3.77,0-7.54,0.07-11.3,0.17c-0.14,0-0.28,0.01-0.42,0.01
c-2.65,0-3.63-0.79-3.62-3.83c0.11-33.16,0.11-66.32,0-99.47c-0.01-3.29,1.09-4.11,4.04-4.11c0.08,0,0.17,0,0.25,0
C146.42,87.67,152,87.72,157.57,87.72L157.57,87.72z M194.36,281.6c1.99-1.26,2-3.28,2.56-4.93c11.12-32.77,22.25-65.54,33.17-98.37
c1.26-3.8,2.8-5.65,6.8-5.65c0.26,0,0.52,0.01,0.8,0.02c3.39,0.18,6.79,0.24,10.24,0.24c4.95,0,10-0.11,15.28-0.11
c-4.67,13.01-9.09,25.35-13.54,37.68c-11.87,32.86-23.84,65.69-35.55,98.6c-1.33,3.74-2.98,5.32-6.79,5.32c-0.2,0-0.41,0-0.62-0.01
c-3.92-0.16-7.85-0.22-11.78-0.22c-4.57,0-9.14,0.08-13.71,0.18c-0.1,0-0.2,0-0.3,0c-2.92,0-4.48-0.97-5.54-3.94
c-8.77-24.43-17.67-48.81-26.65-73.16c-1.07-2.91-1.05-4.42,2.36-5.45c6.85-2.06,13.63-4.38,20.29-6.97
c0.93-0.36,1.68-0.54,2.31-0.54c1.53,0,2.27,1.09,2.96,3.25c5.26,16.48,10.7,32.91,16.08,49.35
C193.22,278.43,193.78,279.95,194.36,281.6L194.36,281.6z"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="LOGO" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve">
<style type="text/css">
.st0{fill:#ffffff;}
</style>
<path class="st0" d="M200,8c25.92,0,51.07,5.08,74.73,15.09c22.86,9.67,43.4,23.51,61.03,41.15c17.64,17.64,31.48,38.17,41.15,61.03
C386.92,148.93,392,174.08,392,200s-5.08,51.07-15.09,74.73c-9.67,22.86-23.51,43.4-41.15,61.03
c-17.64,17.64-38.17,31.48-61.03,41.15C251.07,386.92,225.92,392,200,392s-51.07-5.08-74.73-15.09
c-22.86-9.67-43.4-23.51-61.03-41.15c-17.64-17.64-31.48-38.17-41.15-61.03C13.08,251.07,8,225.92,8,200s5.08-51.07,15.08-74.73
c9.67-22.86,23.51-43.4,41.15-61.03c17.64-17.64,38.17-31.48,61.03-41.15C148.93,13.08,174.08,8,200,8 M157.57,79.72
c-5.34,0-10.77-0.04-16.61-0.13c-0.13,0-0.25,0-0.37,0c-1.39,0-5.63,0-8.83,3.21c-3.23,3.24-3.21,7.53-3.21,8.93
c0.11,32.12,0.12,65.57,0,99.42c0,1.37-0.02,5.55,3.15,8.73c3.12,3.13,7.15,3.13,8.47,3.13c0.21,0,0.42,0,0.63-0.01
c4.16-0.11,7.78-0.17,11.09-0.17c3.53,0,6.76,0.06,9.86,0.19c0.29,0.01,0.57,0.02,0.84,0.02c3.59,0,6.49-1.08,8.61-3.22
c3.27-3.29,3.23-7.73,3.22-9.4c-0.02-2.5-0.04-5.01-0.05-7.53c0.22,0.74,0.43,1.48,0.65,2.22c1.97,6.79,6.32,10.23,12.91,10.23
c0.26,0,0.54-0.01,0.82-0.02c3.58-0.14,7.29-0.22,11.03-0.22c3.68,0,7.5,0.07,11.35,0.21c0.27,0.01,0.54,0.01,0.8,0.01
c1.34,0,2.79-0.15,4.24-0.56c-7.07,21.09-14.23,42.25-21.28,63.04c-3.54-10.78-7.15-21.8-10.62-32.69
c-2.45-7.67-7.68-8.82-10.58-8.82c-1.62,0-3.32,0.35-5.2,1.08c-6.4,2.48-13.03,4.76-19.71,6.77c-3.66,1.1-6.22,3.07-7.61,5.86
c-2.1,4.2-0.59,8.28,0.05,10.02c9.7,26.29,18.41,50.2,26.63,73.09c2.17,6.04,6.69,9.24,13.07,9.24c0.16,0,0.31,0,0.47-0.01
c4.28-0.09,8.89-0.18,13.54-0.18c4.19,0,7.93,0.07,11.45,0.21c0.32,0.01,0.64,0.02,0.94,0.02c10.55,0,13.4-8.01,14.33-10.64
c8.58-24.11,17.45-48.58,26.02-72.25c3.18-8.77,6.35-17.54,9.52-26.31c3.04-8.42,6.07-16.85,9.17-25.5c1.44-4.01,2.9-8.07,4.38-12.2
c0.88-2.45,0.51-5.18-0.99-7.31c-1.05-1.49-2.55-2.55-4.25-3.06c1.05-0.49,1.98-1.15,2.81-1.98c3.08-3.09,3.07-7.13,3.06-8.46
c-0.08-23.52-0.08-44.26,0.01-63.42c0.01-1.34,0.03-5.41-3.11-8.55c-3.12-3.13-7.15-3.13-8.47-3.13l-0.24,0
c-5.84,0.07-11.56,0.1-17,0.1c-5.74,0-11.38-0.04-16.79-0.11l-0.27,0c-4.32,0-9.97,1.6-12.37,9.2c-2.55,8.06-5.3,16.25-7.97,24.17
c-0.94,2.79-1.88,5.58-2.81,8.38c-0.78,2.35-1.57,4.7-2.36,7.07c-4.33-12.9-8.75-26.12-12.99-39.17c-2.08-6.41-6.43-9.66-12.93-9.66
c-0.12,0-0.25,0-0.38,0C168.53,79.67,163.11,79.72,157.57,79.72 M230.91,164.86c0.33,0.18,0.68,0.35,1.03,0.49
c-0.46,0.14-0.91,0.31-1.33,0.49C230.71,165.51,230.81,165.19,230.91,164.86 M200,0C89.54,0,0,89.54,0,200s89.54,200,200,200
s200-89.54,200-200S310.46,0,200,0L200,0z M157.57,87.72c5.42,0,10.84-0.04,16.26-0.14c0.08,0,0.16,0,0.24,0
c3.08,0,4.35,1.14,5.32,4.13c6.06,18.66,12.35,37.24,18.59,55.84c0.5,1.5,1.14,2.96,2.17,5.6c3.53-10.55,6.67-19.91,9.8-29.28
c3.63-10.88,7.36-21.73,10.82-32.67c0.84-2.66,2.02-3.61,4.74-3.61c0.05,0,0.11,0,0.16,0c5.63,0.08,11.26,0.11,16.9,0.11
c5.7,0,11.4-0.04,17.09-0.1c0.05,0,0.1,0,0.15,0c2.66,0,3.59,0.82,3.58,3.65c-0.1,21.16-0.09,42.32-0.01,63.48
c0.01,2.45-0.61,3.48-3.15,3.48c-0.06,0-0.13,0-0.2,0c-3.95-0.08-7.91-0.13-11.86-0.13c-3.71,0-7.42,0.04-11.13,0.14
c-0.09,0-0.17,0-0.26,0c-2.86,0-3.26-1.36-3.22-3.87c0.12-7.45,0.04-14.9,0.04-22.59c-2.19,1.2-1.94,3.31-2.43,4.91
c-4.76,15.41-9.49,30.83-13.94,46.32c-0.91,3.19-2.23,4.33-5.32,4.33c-0.16,0-0.33,0-0.51-0.01c-3.87-0.14-7.76-0.22-11.64-0.22
c-3.79,0-7.57,0.07-11.35,0.22c-0.17,0.01-0.33,0.01-0.49,0.01c-3.2,0-4.33-1.38-5.22-4.47c-4.73-16.28-9.75-32.48-14.68-48.71
c-0.23-0.76-0.55-1.49-1.67-2.2c0,1.51,0,3.03,0,4.54c0,17.99-0.08,35.99,0.07,53.98c0.03,3.18-0.62,4.56-3.82,4.56
c-0.17,0-0.34,0-0.52-0.01c-3.39-0.14-6.79-0.19-10.18-0.19c-3.77,0-7.54,0.07-11.3,0.17c-0.14,0-0.28,0.01-0.42,0.01
c-2.65,0-3.63-0.79-3.62-3.83c0.11-33.16,0.11-66.32,0-99.47c-0.01-3.29,1.09-4.11,4.04-4.11c0.08,0,0.17,0,0.25,0
C146.42,87.67,152,87.72,157.57,87.72L157.57,87.72z M194.36,281.6c1.99-1.26,2-3.28,2.56-4.93c11.12-32.77,22.25-65.54,33.17-98.37
c1.26-3.8,2.8-5.65,6.8-5.65c0.26,0,0.52,0.01,0.8,0.02c3.39,0.18,6.79,0.24,10.24,0.24c4.95,0,10-0.11,15.28-0.11
c-4.67,13.01-9.09,25.35-13.54,37.68c-11.87,32.86-23.84,65.69-35.55,98.6c-1.33,3.74-2.98,5.32-6.79,5.32c-0.2,0-0.41,0-0.62-0.01
c-3.92-0.16-7.85-0.22-11.78-0.22c-4.57,0-9.14,0.08-13.71,0.18c-0.1,0-0.2,0-0.3,0c-2.92,0-4.48-0.97-5.54-3.94
c-8.77-24.43-17.67-48.81-26.65-73.16c-1.07-2.91-1.05-4.42,2.36-5.45c6.85-2.06,13.63-4.38,20.29-6.97
c0.93-0.36,1.68-0.54,2.31-0.54c1.53,0,2.27,1.09,2.96,3.25c5.26,16.48,10.7,32.91,16.08,49.35
C193.22,278.43,193.78,279.95,194.36,281.6L194.36,281.6z"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="LOGO" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M200,0C89.54,0,0,89.54,0,200s89.54,200,200,200s200-89.54,200-200S310.46,0,200,0z M249.67,210.48
c-11.87,32.86-23.84,65.69-35.55,98.6c-1.4,3.94-3.16,5.48-7.41,5.31c-8.48-0.35-16.99-0.22-25.48-0.04
c-3.12,0.07-4.74-0.87-5.85-3.94c-8.77-24.43-17.67-48.81-26.65-73.16c-1.07-2.91-1.05-4.42,2.36-5.45
c6.85-2.06,13.63-4.38,20.29-6.97c3.22-1.25,4.29-0.33,5.26,2.7c5.26,16.48,10.7,32.91,16.08,49.35c0.5,1.54,1.06,3.06,1.64,4.71
c1.99-1.26,2-3.28,2.56-4.93c11.12-32.77,22.25-65.54,33.17-98.37c1.34-4.04,3-5.88,7.6-5.63c8.26,0.45,16.57,0.13,25.52,0.13
C258.54,185.81,254.12,198.15,249.67,210.48z M263.37,154.73c0.01,2.52-0.65,3.54-3.34,3.48c-7.66-0.15-15.33-0.19-22.99,0.01
c-3.09,0.08-3.52-1.28-3.48-3.87c0.12-7.45,0.04-14.9,0.04-22.59c-2.19,1.2-1.94,3.31-2.43,4.91c-4.76,15.41-9.49,30.83-13.94,46.32
c-0.96,3.36-2.37,4.44-5.83,4.32c-7.65-0.28-15.33-0.3-22.99,0.01c-3.58,0.14-4.78-1.21-5.72-4.45
c-4.73-16.28-9.75-32.48-14.68-48.71c-0.23-0.76-0.55-1.49-1.67-2.2c0,1.51,0,3.03,0,4.54c0,18-0.08,35.99,0.07,53.98
c0.03,3.35-0.68,4.69-4.34,4.55c-7.15-0.29-14.33-0.22-21.49-0.02c-2.96,0.08-4.04-0.63-4.03-3.83c0.11-33.16,0.11-66.31,0-99.47
c-0.01-3.38,1.15-4.16,4.29-4.11c10.99,0.16,22,0.19,32.99-0.01c3.26-0.06,4.56,1.07,5.56,4.13c6.06,18.66,12.35,37.24,18.59,55.84
c0.5,1.5,1.14,2.96,2.17,5.6c3.53-10.55,6.67-19.91,9.8-29.28c3.63-10.88,7.36-21.73,10.82-32.67c0.86-2.71,2.07-3.64,4.9-3.61
c11.33,0.15,22.66,0.14,33.99,0.01c2.78-0.03,3.75,0.77,3.73,3.64C263.28,112.41,263.29,133.57,263.37,154.73z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

@ -1,9 +0,0 @@
<svg width="105" height="86" viewBox="0 0 105 86" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.2837 3.64096L26.8176 29.1737C26.2134 29.0336 25.5841 28.9595 24.9375 28.9595C24.1146 28.9595 23.3195 29.0795 22.5689 29.3029L8.39081 8.84949C13.6881 3.38989 21.0532 0 29.1845 0C34.246 0 39.0647 1.28921 43.2837 3.64096ZM46.9351 6.05835C49.0195 7.6791 50.8938 9.59523 52.5 11.7701C54.4052 9.19305 56.6861 6.97916 59.2465 5.18957L73.7803 11.4804C73.6951 11.8884 73.6504 12.3114 73.6504 12.7449C73.6504 12.9329 73.6588 13.1189 73.6753 13.3026L30.79 31.376C30.753 31.3393 30.7157 31.3029 30.6781 31.2668L46.9351 6.05835ZM64.0364 2.48382C67.6681 0.869829 71.6604 0 75.8155 0C77.0207 0 78.2091 0.0745037 79.3763 0.219124L78.3256 6.77513C77.5514 6.96363 76.8347 7.29956 76.2069 7.75176L64.0364 2.48382ZM83.6682 1.08808L82.6681 7.32896C83.3456 7.69367 83.9468 8.18264 84.4414 8.76533L92.5383 5.35289C89.873 3.45615 86.8816 1.99959 83.6682 1.08808ZM96.2946 8.52936L87.0269 12.4352L103.233 19.45C101.735 15.2972 99.3423 11.5768 96.2946 8.52936ZM104.622 24.83L84.8046 16.2522C83.8891 17.5702 82.4717 18.5112 80.8301 18.7971L77.0494 42.3888C78.3244 43.2725 79.2489 44.6286 79.5714 46.2049L97.748 49.9298C99.2057 47.6873 100.572 45.374 101.845 42.9944C103.945 38.8188 105 34.307 105 29.5933C105 27.9722 104.87 26.3811 104.622 24.83ZM95.0512 53.8556L78.8647 50.5385C78.084 51.8798 76.8109 52.8975 75.294 53.3426L71.5864 76.478C80.6376 70.4229 88.5885 62.7696 95.0512 53.8556ZM66.6628 79.55C64.4549 80.833 62.1905 82.0238 59.8745 83.1199L31.5824 42.3059C31.8858 41.9017 32.1534 41.4691 32.3804 41.0126L67.4931 48.2082C67.7536 50.3194 69.0824 52.0986 70.9202 52.985L66.6628 79.55ZM55.7994 84.9285C55.0925 85.2216 54.3814 85.5059 53.666 85.7815C53.2903 85.9272 52.8923 86 52.5 86C52.0745 86 51.66 85.9158 51.2676 85.7534C36.4043 80.0169 23.3503 70.4835 13.3009 58.2123L21.8074 45.0218C22.7735 45.4159 23.8303 45.633 24.9375 45.633C26.0654 45.633 27.1407 45.4077 28.1214 44.9997L55.7994 84.9285ZM10.4519 54.5508L18.3228 42.346C18.1418 42.1078 17.9733 41.8595 17.8182 41.6023L5.33913 46.8615C6.92157 49.5175 8.62866 52.0832 10.4519 54.5508ZM3.171 43.0158L15.9255 37.6405L0.386851 34.4562C0.865721 37.4083 1.79078 40.267 3.171 43.0158ZM0.001427 29.8988C0.000475563 29.7989 0 29.699 0 29.5989C0 23.1653 2.03423 17.2047 5.48465 12.3437L18.8478 31.6217C18.3307 32.1796 17.8889 32.8086 17.5386 33.4927L0.001427 29.8988ZM32.9874 35.2094C33.1118 35.6934 33.1939 36.1944 33.2296 36.7082L68.5385 43.944C69.5032 42.5521 71.0273 41.5794 72.7842 41.3534L76.5335 17.9578C76.2027 17.7504 75.893 17.5122 75.6086 17.2473L32.9874 35.2094Z" fill="url(#paint0_radial)"/>
<defs>
<radialGradient id="paint0_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(-3.52467 41.7884) rotate(90) scale(85.905 121.227)">
<stop stop-color="#20C6B7"/>
<stop offset="1" stop-color="#4D9ABF"/>
</radialGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

@ -1,191 +0,0 @@
'use strict';
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const path = require('path');
const chalk = require('react-dev-utils/chalk');
const fs = require('fs-extra');
const webpack = require('webpack');
const configFactory = require('../config/webpack.config');
const paths = require('../config/paths');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
const printBuildError = require('react-dev-utils/printBuildError');
const measureFileSizesBeforeBuild =
FileSizeReporter.measureFileSizesBeforeBuild;
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
const useYarn = fs.existsSync(paths.yarnLockFile);
// These sizes are pretty large. We'll warn for bundles exceeding them.
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
const isInteractive = process.stdout.isTTY;
// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1);
}
// Generate configuration
const config = configFactory('production');
// We require that you explicitly set browsers and do not fall back to
// browserslist defaults.
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// First, read the current file sizes in build directory.
// This lets us display how much they changed later.
return measureFileSizesBeforeBuild(paths.appBuild);
})
.then(previousFileSizes => {
// Remove all content but keep the directory so that
// if you're in it, you don't end up in Trash
fs.emptyDirSync(paths.appBuild);
// Merge with the public folder
copyPublicFolder();
// Start the webpack build
return build(previousFileSizes);
})
.then(
({ stats, previousFileSizes, warnings }) => {
if (warnings.length) {
console.log(chalk.yellow('Compiled with warnings.\n'));
console.log(warnings.join('\n\n'));
console.log(
'\nSearch for the ' +
chalk.underline(chalk.yellow('keywords')) +
' to learn more about each warning.'
);
console.log(
'To ignore, add ' +
chalk.cyan('// eslint-disable-next-line') +
' to the line before.\n'
);
} else {
console.log(chalk.green('Compiled successfully.\n'));
}
console.log('File sizes after gzip:\n');
printFileSizesAfterBuild(
stats,
previousFileSizes,
paths.appBuild,
WARN_AFTER_BUNDLE_GZIP_SIZE,
WARN_AFTER_CHUNK_GZIP_SIZE
);
console.log();
const appPackage = require(paths.appPackageJson);
const publicUrl = paths.publicUrl;
const publicPath = config.output.publicPath;
const buildFolder = path.relative(process.cwd(), paths.appBuild);
printHostingInstructions(
appPackage,
publicUrl,
publicPath,
buildFolder,
useYarn
);
},
err => {
console.log(chalk.red('Failed to compile.\n'));
printBuildError(err);
process.exit(1);
}
)
.catch(err => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
});
// Create the production build and print the deployment instructions.
function build(previousFileSizes) {
// We used to support resolving modules according to `NODE_PATH`.
// This now has been deprecated in favor of jsconfig/tsconfig.json
// This lets you use absolute paths in imports inside large monorepos:
if (process.env.NODE_PATH) {
console.log(
chalk.yellow(
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
)
);
console.log();
}
console.log('Creating an optimized production build...');
const compiler = webpack(config);
return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
let messages;
if (err) {
if (!err.message) {
return reject(err);
}
messages = formatWebpackMessages({
errors: [err.message],
warnings: [],
});
} else {
messages = formatWebpackMessages(
stats.toJson({ all: false, warnings: true, errors: true })
);
}
if (messages.errors.length) {
// Only keep the first error. Others are often indicative
// of the same problem, but confuse the reader with noise.
if (messages.errors.length > 1) {
messages.errors.length = 1;
}
return reject(new Error(messages.errors.join('\n\n')));
}
if (
process.env.CI &&
(typeof process.env.CI !== 'string' ||
process.env.CI.toLowerCase() !== 'false') &&
messages.warnings.length
) {
console.log(
chalk.yellow(
'\nTreating warnings as errors because process.env.CI = true.\n' +
'Most CI servers set it automatically.\n'
)
);
return reject(new Error(messages.warnings.join('\n\n')));
}
return resolve({
stats,
previousFileSizes,
warnings: messages.warnings,
});
});
});
}
function copyPublicFolder() {
fs.copySync(paths.appPublic, paths.appBuild, {
dereference: true,
filter: file => file !== paths.appHtml,
});
}

@ -1,145 +0,0 @@
'use strict';
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const fs = require('fs');
const chalk = require('react-dev-utils/chalk');
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const clearConsole = require('react-dev-utils/clearConsole');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const {
choosePort,
createCompiler,
prepareProxy,
prepareUrls,
} = require('react-dev-utils/WebpackDevServerUtils');
const openBrowser = require('react-dev-utils/openBrowser');
const paths = require('../config/paths');
const configFactory = require('../config/webpack.config');
const createDevServerConfig = require('../config/webpackDevServer.config');
const useYarn = fs.existsSync(paths.yarnLockFile);
const isInteractive = process.stdout.isTTY;
// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1);
}
// Tools like Cloud9 rely on this.
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
const HOST = process.env.HOST || '0.0.0.0';
if (process.env.HOST) {
console.log(
chalk.cyan(
`Attempting to bind to HOST environment variable: ${chalk.yellow(
chalk.bold(process.env.HOST)
)}`
)
);
console.log(
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
);
console.log(
`Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}`
);
console.log();
}
// We require that you explicitly set browsers and do not fall back to
// browserslist defaults.
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// We attempt to use the default port but if it is busy, we offer the user to
// run on a different port. `choosePort()` Promise resolves to the next free port.
return choosePort(HOST, DEFAULT_PORT);
})
.then(port => {
if (port == null) {
// We have not found a port.
return;
}
const config = configFactory('development');
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const appName = require(paths.appPackageJson).name;
const useTypeScript = fs.existsSync(paths.appTsConfig);
const urls = prepareUrls(protocol, HOST, port);
const devSocket = {
warnings: warnings =>
devServer.sockWrite(devServer.sockets, 'warnings', warnings),
errors: errors =>
devServer.sockWrite(devServer.sockets, 'errors', errors),
};
// Create a webpack compiler that is configured with custom messages.
const compiler = createCompiler({
appName,
config,
devSocket,
urls,
useYarn,
useTypeScript,
webpack,
});
// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
// Serve webpack assets generated by the compiler over a web server.
const serverConfig = createDevServerConfig(
proxyConfig,
urls.lanUrlForConfig
);
const devServer = new WebpackDevServer(compiler, serverConfig);
// Launch WebpackDevServer.
devServer.listen(port, HOST, err => {
if (err) {
return console.log(err);
}
if (isInteractive) {
clearConsole();
}
// We used to support resolving modules according to `NODE_PATH`.
// This now has been deprecated in favor of jsconfig/tsconfig.json
// This lets you use absolute paths in imports inside large monorepos:
if (process.env.NODE_PATH) {
console.log(
chalk.yellow(
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
)
);
console.log();
}
console.log(chalk.cyan('Starting the development server...\n'));
openBrowser(urls.localUrlForBrowser);
});
['SIGINT', 'SIGTERM'].forEach(function(sig) {
process.on(sig, function() {
devServer.close();
process.exit();
});
});
})
.catch(err => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
});

@ -1,52 +0,0 @@
'use strict';
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'test';
process.env.NODE_ENV = 'test';
process.env.PUBLIC_URL = '';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const jest = require('jest');
const execSync = require('child_process').execSync;
let argv = process.argv.slice(2);
function isInGitRepository() {
try {
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
return true;
} catch (e) {
return false;
}
}
function isInMercurialRepository() {
try {
execSync('hg --cwd . root', { stdio: 'ignore' });
return true;
} catch (e) {
return false;
}
}
// Watch unless on CI or explicitly running all tests
if (
!process.env.CI &&
argv.indexOf('--watchAll') === -1
) {
// https://github.com/facebook/create-react-app/issues/5210
const hasSourceControl = isInGitRepository() || isInMercurialRepository();
argv.push(hasSourceControl ? '--watch' : '--watchAll');
}
jest.run(argv);

@ -1,228 +0,0 @@
const api = {
urlServer:
process.env.NEXT_PUBLIC_SERVER_URL || "https://demo.mieuxvoter.fr/api/",
feedbackForm:
process.env.NEXT_PUBLIC_FEEDBACK_FORM ||
"https://docs.google.com/forms/d/e/1FAIpQLScuTsYeBXOSJAGSE_AFraFV7T2arEYua7UCM4NRBSCQQfRB6A/viewform",
routesServer: {
setElection: "election/",
getElection: "election/get/:slug/",
getResults: "election/results/:slug",
voteElection: "election/vote/",
},
};
const sendInviteMail = (res) => {
/**
* Send an invitation mail using a micro-service with Netlify
*/
const { id, title, mails, tokens, locale } = res;
if (!mails || !mails.length) {
throw new Error("No emails are provided.");
}
if (mails.length !== tokens.length) {
throw new Error("The number of emails differ from the number of tokens");
}
const origin =
typeof window !== "undefined" && window.location.origin
? window.location.origin
: "http://localhost";
const urlVote = (pid, token) => new URL(`/vote/${pid}/${token}`, origin);
const urlResult = (pid) => new URL(`/result/${pid}`, origin);
const recipientVariables = {};
tokens.forEach((token, index) => {
recipientVariables[mails[index]] = {
urlVote: urlVote(id, token),
urlResult: urlResult(id),
};
});
const req = fetch("/.netlify/functions/send-invite-email/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
recipientVariables,
title,
locale,
}),
});
return req.then((any) => res);
};
const createElection = (
title,
candidates,
{
/**
* Create an election from its title, its candidates and a bunch of options
*/
mails,
numGrades,
start,
finish,
restrictResult,
locale,
},
successCallback,
failureCallback
) => {
const endpoint = new URL(api.routesServer.setElection, api.urlServer);
console.log(endpoint.href);
const onInvitationOnly = mails && mails.length > 0;
fetch(endpoint.href, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title,
candidates,
on_invitation_only: onInvitationOnly,
num_grades: numGrades,
elector_emails: mails || [],
start_at: start,
finish_at: finish,
select_language: locale || "en",
front_url: window.location.origin,
restrict_results: restrictResult,
send_mail: false,
}),
})
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response.json();
})
.then((res) => {
if (onInvitationOnly) {
return sendInviteMail({ locale, mails: mails, ...res });
}
return res;
})
.then(successCallback)
.catch(failureCallback || console.log);
};
const getResults = (pid, successCallback, failureCallback) => {
/**
* Fetch results from external API
*/
const endpoint = new URL(
api.routesServer.getResults.replace(new RegExp(":slug", "g"), pid),
api.urlServer
);
return fetch(endpoint.href)
.then((response) => {
if (!response.ok) {
return Promise.reject(response.text());
}
return response.json();
})
.then(successCallback || ((res) => res))
.catch(failureCallback || ((err) => err));
};
const getDetails = (pid, successCallback, failureCallback) => {
/**
* Fetch data from external API
*/
const detailsEndpoint = new URL(
api.routesServer.getElection.replace(new RegExp(":slug", "g"), pid),
api.urlServer
);
return fetch(detailsEndpoint.href)
.then((response) => {
if (!response.ok) {
return Promise.reject(response.text());
}
return response.json();
})
.then(successCallback || ((res) => res))
.catch(failureCallback || ((err) => err))
.then((res) => res);
};
const castBallot = (judgments, pid, token, callbackSuccess, callbackError) => {
/**
* Save a ballot on the remote database
*/
const endpoint = new URL(api.routesServer.voteElection, api.urlServer);
const payload = {
election: pid,
grades_by_candidate: judgments,
};
if (token && token !== "") {
payload["token"] = token;
}
fetch(endpoint.href, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
})
.then(callbackSuccess || ((res) => res))
.catch(callbackError || console.log);
};
export const UNKNOWN_ELECTION_ERROR = "E1:";
export const ONGOING_ELECTION_ERROR = "E2:";
export const NO_VOTE_ERROR = "E3:";
export const ELECTION_NOT_STARTED_ERROR = "E4:";
export const ELECTION_FINISHED_ERROR = "E5:";
export const INVITATION_ONLY_ERROR = "E6:";
export const UNKNOWN_TOKEN_ERROR = "E7:";
export const USED_TOKEN_ERROR = "E8:";
export const WRONG_ELECTION_ERROR = "E9:";
export const apiErrors = (error, t) => {
if (error.includes(UNKNOWN_ELECTION_ERROR)) {
return t("error.e1");
}
if (error.includes(ONGOING_ELECTION_ERROR)) {
return t("error.e2");
}
if (error.includes(NO_VOTE_ERROR)) {
return t("error.e3");
}
if (error.includes(ELECTION_NOT_STARTED_ERROR)) {
return t("error.e4");
}
if (error.includes(ELECTION_FINISHED_ERROR)) {
return t("error.e5");
}
if (error.includes(INVITATION_ONLY_ERROR)) {
return t("error.e6");
}
if (error.includes(USED_TOKEN_ERROR)) {
return t("error.e7");
}
if (error.includes(WRONG_ELECTION_ERROR)) {
return t("error.e8");
} else {
return t("error.catch22");
}
};
export {
api,
getDetails,
getResults,
createElection,
sendInviteMail,
castBallot,
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save