diff --git a/.gitignore b/.gitignore index cab46f5..843e064 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ yarn-error.log* # Local Netlify folder .netlify functions/next_* +.env diff --git a/pages/vote/[pid]/[[...tid]].jsx b/pages/vote/[pid]/[[...tid]].jsx index 28ee186..943f472 100644 --- a/pages/vote/[pid]/[[...tid]].jsx +++ b/pages/vote/[pid]/[[...tid]].jsx @@ -8,6 +8,7 @@ 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"; @@ -17,8 +18,14 @@ export async function getServerSideProps({ query: { pid, tid }, locale }) { const [res, translations] = await Promise.all([ getDetails( pid, - (res) => ({ ok: true, ...res }), - (err) => ({ ok: false, err }) + (res) => { + console.log("DETAILS:", res); + return { ok: true, ...res }; + }, + (err) => { + console.log("ERR:", err); + return { ok: false, err: "Unknown error" }; + } ), serverSideTranslations(locale, [], config), ]); diff --git a/services/api.js b/services/api.js index 8f77576..5939a14 100644 --- a/services/api.js +++ b/services/api.js @@ -146,9 +146,12 @@ const getDetails = (pid, successCallback, failureCallback) => { return fetch(detailsEndpoint.href) .then((response) => { if (!response.ok) { + console.log("NOK", response); return Promise.reject(response.text()); } - return response.json(); + const res = response.json(); + console.log("OK", res); + return res; }) .then(successCallback || ((res) => res)) .catch(failureCallback || ((err) => err)); diff --git a/src/App.css b/src/App.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/App.jsx b/src/App.jsx deleted file mode 100644 index f934cd1..0000000 --- a/src/App.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from "react"; - -import Routes from "./Routes"; -import Header from "./components/layouts/Header"; -import Footer from "./components/layouts/Footer"; -import AppContextProvider from "./AppContext"; - -function App() { - return ( - -
-
- -
-
- ); -} - -export default App; diff --git a/src/App.test.jsx b/src/App.test.jsx deleted file mode 100644 index fde35e9..0000000 --- a/src/App.test.jsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import { MemoryRouter } from "react-router-dom"; - -import Routes from "./Routes"; -import App from "./App"; -import Adapter from "enzyme-adapter-react-16"; -import { mount, configure } from "enzyme"; - -import Home from "./components/views/Home"; -import CreateElection from "./components/views/CreateElection"; -import Result from "./components/views/Result"; -import Vote from "./components/views/Vote"; -import UnknownView from "./components/views/UnknownView"; - -configure({ adapter: new Adapter() }); - -it("renders without crashing", () => { - const div = document.createElement("div"); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); - -describe("open good View component for each route", () => { - it("should show Home view component for `/`", () => { - const wrapper = mount( - - - - ); - expect(wrapper.find(Home)).toHaveLength(1); - expect(wrapper.find(UnknownView)).toHaveLength(0); - }); - - it("should show CreateElection view component for `/create-election`", () => { - const wrapper = mount( - - - - ); - expect(wrapper.find(CreateElection)).toHaveLength(1); - expect(wrapper.find(UnknownView)).toHaveLength(0); - }); - - //this test is not good because window.location.search is empty even there is ?title= parameter in route - //Clement : I don't know how to achieve this test for now (maybe the component using window.location.search is not a good practice) - /*it("should show CreateElection view component with title for `/create-election/?title=test%20with%20title`", () => { - const wrapper = mount( - - - - ); - expect(wrapper.find(CreateElection)).toHaveLength(1); - expect(wrapper.find('input[name="title"]').props().value).toBe( - "test with title" - ); - expect(wrapper.find(UnknownView)).toHaveLength(0); - });*/ - - it("should show UnknownView view component for `/vote`", () => { - const wrapper = mount( - - - - ); - expect(wrapper.find(Vote)).toHaveLength(0); - expect(wrapper.find(UnknownView)).toHaveLength(1); - }); - - it("should show UnknownView view component for `/result`", () => { - const wrapper = mount( - - - - ); - expect(wrapper.find(Result)).toHaveLength(0); - expect(wrapper.find(UnknownView)).toHaveLength(1); - }); - - it("should show UnknownView view component for `/aaabbbcccddd`", () => { - const wrapper = mount( - - - - ); - expect(wrapper.find(UnknownView)).toHaveLength(1); - }); -}); diff --git a/src/AppContext.jsx b/src/AppContext.jsx deleted file mode 100644 index 9ecc10d..0000000 --- a/src/AppContext.jsx +++ /dev/null @@ -1,29 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { createContext, Suspense } from "react"; -import { BrowserRouter as Router } from "react-router-dom"; -import Loader from "./components/loader"; - -export const AppContext = createContext(); - -const AppContextProvider = ({ children }) => { - const defaultState = { - urlServer: process.env.REACT_APP_SERVER_URL, - feedbackForm: process.env.REACT_APP_FEEDBACK_FORM, - routesServer: { - setElection: "election/", - getElection: "election/get/:slug/", - getResultsElection: "election/results/:slug", - voteElection: "election/vote/" - } - }; - return ( - }> - - - {children} - - - - ); -}; -export default AppContextProvider; diff --git a/src/Errors.js b/src/Errors.js deleted file mode 100644 index 8b9afa2..0000000 --- a/src/Errors.js +++ /dev/null @@ -1,61 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React from "react"; -import { Container, Row, Col } from "reactstrap"; -import { Link } from "react-router-dom"; -import logoLine from "./logos/logo-line-white.svg"; - -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 redirectError = () => {}; - -export const errorMessage = (error, t) => { - if (error.startsWith(UNKNOWN_ELECTION_ERROR)) { - return t("Oops... The election is unknown."); - } else if (error.startsWith(ONGOING_ELECTION_ERROR)) { - return t( - "The election is still going on. You can't access now to the results." - ); - } else if (error.startsWith(NO_VOTE_ERROR)) { - return t("No votes have been recorded yet. Come back later."); - } else if (error.startsWith(ELECTION_NOT_STARTED_ERROR)) { - return t("The election has not started yet."); - } else if (error.startsWith(ELECTION_FINISHED_ERROR)) { - return t("The election is over. You can't vote anymore"); - } else if (error.startsWith(INVITATION_ONLY_ERROR)) { - return t("You need a token to vote in this election"); - } else if (error.startsWith(USED_TOKEN_ERROR)) { - return t("You seem to have already voted."); - } else if (error.startsWith(WRONG_ELECTION_ERROR)) { - return t("The parameters of the election are incorrect."); - } -}; - -export const Error = props => ( - - - - logo - - - - -

{props.value}

- -
- - - - Back to home page - - - -
-); diff --git a/src/Routes.jsx b/src/Routes.jsx deleted file mode 100644 index 61b453b..0000000 --- a/src/Routes.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from "react"; -import { Switch, Route } from "react-router-dom"; - -import Home from "./components/views/Home"; -import CreateElection from "./components/views/CreateElection"; -import Vote from "./components/views/Vote"; -import Result from "./components/views/Result"; -import UnknownView from "./components/views/UnknownView"; -import UnknownElection from "./components/views/UnknownElection"; -import CreateSuccess from "./components/views/CreateSuccess"; -import VoteSuccess from "./components/views/VoteSuccess"; -import LegalNotices from "./components/views/LegalNotices"; -import PrivacyPolicy from "./components/views/PrivacyPolicy"; -import Faq from "./components/views/Faq"; - - -function Routes() { - return ( -
- - - - - - ( - - )} - /> - ( - - )} - /> - - - - - - - -
- ); -} - -export default Routes; diff --git a/src/Util.jsx b/src/Util.jsx deleted file mode 100644 index 3b55945..0000000 --- a/src/Util.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import i18n from "./i18n.jsx"; - -const colors = [ - "#015411", - "#019812", - "#6bca24", - "#ffb200", - "#ff5d00", - "#b20616", - "#6f0214" -]; - -const gradeNames = [ - "Excellent", - "Very good", - "Good", - "Fair", - "Passable", - "Insufficient", - "To reject" -]; - -const gradeValues = [6, 5, 4, 3, 2, 1, 0]; - -export const grades = gradeNames.map((name, i) => ({ - label: name, - color: colors[i], - value: gradeValues[i] -})); - -export const i18nGrades = () => { - return gradeNames.map((name, i) => ({ - label: i18n.t(name), - color: colors[i], - value: gradeValues[i] - })); -}; diff --git a/src/components/CopyField.jsx b/src/components/CopyField.jsx deleted file mode 100644 index 8009343..0000000 --- a/src/components/CopyField.jsx +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React from "react"; -import { Button } from "reactstrap"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -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 } = props; - - return ( -
- - -
- -
- {/*
- - - {t("Open")} - -
*/} -
- ); -}; - -export default CopyField; diff --git a/src/components/banner/Facebook.jsx b/src/components/banner/Facebook.jsx deleted file mode 100644 index e710a23..0000000 --- a/src/components/banner/Facebook.jsx +++ /dev/null @@ -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 ( - - ); -}; - -export default Facebook; - -//i diff --git a/src/components/banner/Gform.jsx b/src/components/banner/Gform.jsx deleted file mode 100644 index 34454e9..0000000 --- a/src/components/banner/Gform.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { useContext } from "react"; -import PropTypes from 'prop-types'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import {faCommentAlt} from "@fortawesome/free-solid-svg-icons"; -import { AppContext } from "../../AppContext" - - -const Gform = (props) => { - const context = useContext(AppContext); - console.log(context); - - return ( - - - Votre avis nous intéresse ! - - ); -} - -Gform.propTypes = { - className: PropTypes.string, -}; - -export default Gform; diff --git a/src/components/banner/Helloasso.jsx b/src/components/banner/Helloasso.jsx deleted file mode 100644 index eeaa595..0000000 --- a/src/components/banner/Helloasso.jsx +++ /dev/null @@ -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 ( - - support us on helloasso - - ); -}; - -export default Helloasso; diff --git a/src/components/banner/Paypal.jsx b/src/components/banner/Paypal.jsx deleted file mode 100644 index e0d5260..0000000 --- a/src/components/banner/Paypal.jsx +++ /dev/null @@ -1,44 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React from "react"; -import i18n from "../../i18n"; -import { withTranslation } from "react-i18next"; - -import { faPaypal } from "@fortawesome/free-brands-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -const Paypal = props => { - const { t } = props; - let localeStringShort = i18n.language? i18n.language.substring(0, 2): "en"; - let localeStringComplete = - localeStringShort.toLowerCase() + "_" + localeStringShort.toUpperCase(); - if (localeStringComplete === "en_EN") { - localeStringComplete = "en_US"; - } - const pixelLink = - "https://www.paypal.com/" + localeStringComplete + "/i/scr/pixel.gif"; - - return ( -
-
- - - - -
-
- ); -}; - -export default withTranslation()(Paypal); diff --git a/src/components/flag.js b/src/components/flag.js deleted file mode 100644 index df68eaf..0000000 --- a/src/components/flag.js +++ /dev/null @@ -1,4 +0,0 @@ -import * as React from "react"; -import FlagIconFactory from "react-flag-icon-css"; - -export const FlagIcon = FlagIconFactory(React, { useCssModules: false }); diff --git a/src/components/form/ButtonWithConfirm.jsx b/src/components/form/ButtonWithConfirm.jsx deleted file mode 100644 index 4bac4a7..0000000 --- a/src/components/form/ButtonWithConfirm.jsx +++ /dev/null @@ -1,62 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import ModalConfirm from "./ModalConfirm"; - -class ButtonWithConfirm extends Component { - constructor(props) { - super(props); - this._modalConfirm = React.createRef(); - this.state = { - focused: false - }; - } - - getComponent = key => { - return this.props.children.filter(comp => { - return comp.key === key; - }); - }; - - render() { - const classNames = this.props.className.split(" "); - - let classNameForDiv = ""; - let classNameForButton = ""; - classNames.forEach(function(className) { - if ( - className === "input-group-prepend" || - className === "input-group-append" - ) { - classNameForDiv += " " + className; - } else { - classNameForButton += " " + className; - } - }); - - return ( -
- - -
{this.getComponent("modal-title")}
-
{this.getComponent("modal-body")}
-
{this.getComponent("modal-confirm")}
-
{this.getComponent("modal-cancel")}
-
-
- ); - } -} - -export default ButtonWithConfirm; diff --git a/src/components/form/HelpButton.jsx b/src/components/form/HelpButton.jsx deleted file mode 100644 index 1d87b53..0000000 --- a/src/components/form/HelpButton.jsx +++ /dev/null @@ -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 ( - - - {this.state.tooltipOpen ? ( - - - {this.props.children} - - ) : ( - - )} - - - - ); - } -} -export default HelpButton; diff --git a/src/components/form/ModalConfirm.jsx b/src/components/form/ModalConfirm.jsx deleted file mode 100644 index 0d6b63b..0000000 --- a/src/components/form/ModalConfirm.jsx +++ /dev/null @@ -1,53 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap"; - -class ModalConfirm extends Component { - constructor(props) { - super(props); - this.state = { - modal: false - }; - } - - toggle = () => { - this.setState({ - modal: !this.state.modal - }); - }; - - getComponent = key => { - return this.props.children.filter(comp => { - return comp.key === key; - }); - }; - - render() { - return ( - - - {this.getComponent("title")} - - {this.getComponent("body")} - - - - - - ); - } -} - -export default ModalConfirm; diff --git a/src/components/layouts/Footer.jsx b/src/components/layouts/Footer.jsx deleted file mode 100644 index 0b93439..0000000 --- a/src/components/layouts/Footer.jsx +++ /dev/null @@ -1,97 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React from "react"; -import { withTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; -import Paypal from "../banner/Paypal"; -import { useBbox } from "./useBbox"; -import "./footer.css"; - -const Footer = props => { - const linkStyle = { whiteSpace: "nowrap" }; - const { t } = props; - - 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 ( - - ); -}; -export default withTranslation()(Footer); diff --git a/src/components/layouts/Header.jsx b/src/components/layouts/Header.jsx deleted file mode 100644 index 5ed5ca7..0000000 --- a/src/components/layouts/Header.jsx +++ /dev/null @@ -1,62 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import { Collapse, Navbar, NavbarToggler, Nav, NavItem } from "reactstrap"; -import { Link } from "react-router-dom"; -import { withTranslation } from "react-i18next"; - -import logo from "../../logos/logo-color.svg"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faRocket } from "@fortawesome/free-solid-svg-icons"; -import LanguageSelector from "./LanguageSelector"; - -class Header extends Component { - state = { - isOpen: false - }; - - toggle = () => { - this.setState({ - isOpen: !this.state.isOpen - }); - }; - - render() { - const { t } = this.props; - return ( -
- - -
-
- logo -
-
-
-

- {t("Voting platform")} - {t("Majority Judgment")} -

-
-
-
- - - - - -
-
- ); - } -} -export default withTranslation()(Header); diff --git a/src/components/layouts/LanguageSelector.jsx b/src/components/layouts/LanguageSelector.jsx deleted file mode 100644 index cbeeaea..0000000 --- a/src/components/layouts/LanguageSelector.jsx +++ /dev/null @@ -1,30 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React from "react"; -import ReactFlagsSelect from "react-flags-select"; -import "react-flags-select/css/react-flags-select.css"; - -import i18n from "../../i18n"; - -const LanguageSelector = () => { - const selectHandler = e => { - let locale = e.toLowerCase(); - if (locale === "gb") locale = "en"; - i18n.changeLanguage(locale); - }; - - let locale = i18n.language? i18n.language.substring(0, 2).toUpperCase() : "EN"; - if (locale === "EN") locale = "GB"; - return ( - - ); -}; - -export default LanguageSelector; diff --git a/src/components/layouts/footer.css b/src/components/layouts/footer.css deleted file mode 100644 index 45a16dd..0000000 --- a/src/components/layouts/footer.css +++ /dev/null @@ -1,25 +0,0 @@ -.tacky { - margin: 0; - padding: 0; - list-style-type: none; -} - -.tacky li { - display: inline-block; -} - -.tacky li:after { - content: "-"; - margin: 0 5px; -} - -.tacky li:last-of-type:after { - content: ""; - margin: 0; -} - -.tacky li.no-tack:after { - content: ""; - margin: 0; - display: none; -} diff --git a/src/components/layouts/useBbox.jsx b/src/components/layouts/useBbox.jsx deleted file mode 100644 index 42e11f8..0000000 --- a/src/components/layouts/useBbox.jsx +++ /dev/null @@ -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]; -}; \ No newline at end of file diff --git a/src/components/loader/index.jsx b/src/components/loader/index.jsx deleted file mode 100644 index 607a9d1..0000000 --- a/src/components/loader/index.jsx +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React from "react"; -import logo from "./loader-pulse-2.gif"; -import "./style.css"; - -const Loader = () => { - return ( -
- Loading... -
- ); -}; - -export default Loader; diff --git a/src/components/loader/loader-pulse-2-alpha.gif b/src/components/loader/loader-pulse-2-alpha.gif deleted file mode 100644 index a2fc0a2..0000000 Binary files a/src/components/loader/loader-pulse-2-alpha.gif and /dev/null differ diff --git a/src/components/loader/loader-pulse-2.gif b/src/components/loader/loader-pulse-2.gif deleted file mode 100644 index e9a4c48..0000000 Binary files a/src/components/loader/loader-pulse-2.gif and /dev/null differ diff --git a/src/components/loader/style.css b/src/components/loader/style.css deleted file mode 100644 index aad6adb..0000000 --- a/src/components/loader/style.css +++ /dev/null @@ -1,18 +0,0 @@ -.loader { - top:0; - left:0; - height:100%; - width:100%; - position:fixed; - z-index:15; -} - -.loader > img { - width:150px; - height:150px; - position:absolute; - margin:-75px 0 0 -75px; - top:50%; - left:50%; - z-index:16; -} diff --git a/src/components/views/CreateElection.jsx b/src/components/views/CreateElection.jsx deleted file mode 100644 index 538b81d..0000000 --- a/src/components/views/CreateElection.jsx +++ /dev/null @@ -1,896 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import { Redirect, withRouter } from "react-router-dom"; -import { - Collapse, - Container, - Row, - Col, - Input, - Label, - InputGroup, - InputGroupAddon, - Button, - Card, - CardBody -} from "reactstrap"; -import { withTranslation } from "react-i18next"; -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 { resolve } from "url"; -import queryString from "query-string"; -import { - arrayMove, - sortableContainer, - sortableElement, - sortableHandle -} from "react-sortable-hoc"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faPlus, - faTrashAlt, - faCheck, - faCogs, - faExclamationTriangle -} from "@fortawesome/free-solid-svg-icons"; -import { i18nGrades } from "../../Util"; -import { AppContext } from "../../AppContext"; -import HelpButton from "../form/HelpButton"; -import ButtonWithConfirm from "../form/ButtonWithConfirm"; -import Loader from "../wait"; -import i18n from "../../i18n"; - -// 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 DragHandle = sortableHandle(({ children }) => ( - {children} -)); - -const displayClockOptions = () => - Array(24) - .fill(1) - .map((x, i) => ( - - )); - -const SortableCandidate = sortableElement( - ({ candidate, sortIndex, form, t }) => ( -
  • - - - - - - {sortIndex + 1} - - - form.editCandidateLabel(event, sortIndex)} - onKeyPress={event => - form.handleKeypressOnCandidateLabel(event, sortIndex) - } - placeholder={t("Candidate/proposal name...")} - tabIndex={sortIndex + 1} - innerRef={ref => (form.candidateInputs[sortIndex] = ref)} - maxLength="250" - /> - -
    - -
    -
    {t("Delete?")}
    -
    - {t("Are you sure to delete")}{" "} - {candidate.label !== "" ? ( - "{candidate.label}" - ) : ( - - {t("the row")} {sortIndex + 1} - - )}{" "} - ? -
    -
    form.removeCandidate(sortIndex)} - > - Oui -
    -
    Non
    -
    -
    - - - - {t( - "Enter the name of your candidate or proposal here (250 characters max.)" - )} - - -
    -
  • - ) -); - -const SortableCandidatesContainer = sortableContainer(({ items, form, t }) => { - return ( -
      - {items.map((candidate, index) => ( - - ))} -
    - ); -}); - -class CreateElection extends Component { - static contextType = AppContext; - constructor(props) { - super(props); - // default value : start at the last hour - const now = new Date(); - const start = new Date( - now.getTime() - minutes(now) - seconds(now) - ms(now) - ); - const { title } = queryString.parse(this.props.location.search); - - this.state = { - candidates: [{ label: "" }, { label: "" }], - title: title || "", - isVisibleTipsDragAndDropCandidate: true, - numGrades: 7, - waiting: false, - successCreate: false, - redirectTo: null, - isAdvancedOptionsOpen: false, - restrictResult: false, - isTimeLimited: false, - start, - // by default, the election ends in a week - finish: new Date(start.getTime() + 7 * 24 * 3600 * 1000), - electorEmails: [] - }; - this.candidateInputs = []; - this.focusInput = React.createRef(); - this.handleSubmit = this.handleSubmit.bind(this); - this.handleRestrictResultCheck = this.handleRestrictResultCheck.bind(this); - this.handleIsTimeLimited = this.handleIsTimeLimited.bind(this); - } - - handleChangeTitle = event => { - this.setState({ title: event.target.value }); - }; - - handleIsTimeLimited = event => { - this.setState({ isTimeLimited: event.target.value === "1" }); - }; - - handleRestrictResultCheck = event => { - this.setState({ restrictResult: event.target.value === "1" }); - }; - - addCandidate = event => { - let candidates = this.state.candidates; - if (candidates.length < 100) { - candidates.push({ label: "" }); - this.setState({ candidates: candidates }); - } - if (event.type === "keypress") { - setTimeout(() => { - this.candidateInputs[this.state.candidates.length - 1].focus(); - }, 250); - } - }; - - removeCandidate = index => { - let candidates = this.state.candidates; - candidates.splice(index, 1); - if (candidates.length === 0) { - candidates = [{ label: "" }]; - } - this.setState({ candidates: candidates }); - }; - - editCandidateLabel = (event, index) => { - let candidates = this.state.candidates; - candidates[index].label = event.currentTarget.value; - candidates.map(candidate => { - return candidate.label; - }); - this.setState({ - candidates: candidates - }); - }; - - handleKeypressOnCandidateLabel = (event, index) => { - if (event.key === "Enter") { - event.preventDefault(); - if (index + 1 === this.state.candidates.length) { - this.addCandidate(event); - } else { - this.candidateInputs[index + 1].focus(); - } - } - }; - - onCandidatesSortEnd = ({ oldIndex, newIndex }) => { - let candidates = this.state.candidates; - candidates = arrayMove(candidates, oldIndex, newIndex); - this.setState({ candidates: candidates }); - }; - - handleChangeNumGrades = event => { - this.setState({ numGrades: event.target.value }); - }; - - toggleAdvancedOptions = () => { - this.setState({ isAdvancedOptionsOpen: !this.state.isAdvancedOptionsOpen }); - }; - - checkFields() { - const { candidates, title } = this.state; - 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" }; - } - - handleSubmit() { - const { - candidates, - title, - numGrades, - electorEmails - } = this.state; - - let { - start, - finish, - } = this.state; - - const endpoint = resolve( - this.context.urlServer, - this.context.routesServer.setElection - ); - - if(!this.state.isTimeLimited){ - let now = new Date(); - start = new Date( - now.getTime() - minutes(now) - seconds(now) - ms(now) - ); - finish=new Date(start.getTime() + 10 * 365 * 24 * 3600 * 1000); - } - - const { t } = this.props; - const locale = - i18n.language.substring(0, 2).toLowerCase() === "fr" ? "fr" : "en"; - - const check = this.checkFields(); - if (!check.ok) { - toast.error(t(check.msg), { - position: toast.POSITION.TOP_CENTER - }); - return; - } - - this.setState({ waiting: true }); - - fetch(endpoint, { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - title: title, - candidates: candidates.map(c => c.label).filter(c => c !== ""), - on_invitation_only: electorEmails.length > 0, - num_grades: numGrades, - elector_emails: electorEmails, - start_at: start.getTime() / 1000, - finish_at: finish.getTime() / 1000, - select_language: locale, - front_url: window.location.origin, - restrict_results: this.state.restrictResult - }) - }) - .then(response => response.json()) - .then(result => { - if (result.id) { - const nextPage = - electorEmails && electorEmails.length - ? `/link/${result.id}` - : `/links/${result.id}`; - this.setState(() => ({ - redirectTo: nextPage, - successCreate: true, - waiting: false - })); - } else { - toast.error(t("Unknown error. Try again please."), { - position: toast.POSITION.TOP_CENTER - }); - this.setState({ waiting: false }); - } - }) - .catch(error => error); - } - - handleSendNotReady = msg => { - const { t } = this.props; - toast.error(t(msg), { - position: toast.POSITION.TOP_CENTER - }); - }; - - render() { - const { - successCreate, - redirectTo, - waiting, - title, - start, - finish, - candidates, - numGrades, - isAdvancedOptionsOpen, - electorEmails - } = this.state; - const { t } = this.props; - - const grades = i18nGrades(); - const check = this.checkFields(); - - if (successCreate) return ; - - return ( - - - {waiting ? : ""} -
    - - -

    {t("Start an election")}

    - -
    -
    - - - - - - - - - - {t( - "Write here your question or introduce simple your election (250 characters max.)" - )} -
    - {t("For example:")}{" "} - - {t( - "For the role of my representative, I judge this candidate..." - )} - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - -
    - - - - {t("Starting date")} - - - { - this.setState({ - start: new Date( - timeMinusDate(start) + - new Date(e.target.valueAsNumber).getTime() - ) - }); - }} - /> - - - - - - - - - - {t("Ending date")} - - - { - this.setState({ - finish: new Date( - timeMinusDate(finish) + - new Date(e.target.valueAsNumber).getTime() - ) - }); - }} - /> - - - - - -
    -
    - - - {t("Grades")} - - - - - - - {t( - "You can select here the number of grades for your election" - )} -
    - {t("For example:")}{" "} - - {" "} - {t("5 = Excellent, Very good, Good, Fair, Passable")} - -
    - - - {grades.map((mention, i) => { - return ( - - {mention.label} - - ); - })} - -
    -
    - - - {t("Participants")} - - - { - this.setState({ electorEmails: _emails }); - }} - validateEmail={email => { - return isEmail(email); // return boolean - }} - getLabel={(email, index, removeEmail) => { - return ( -
    - {email} - removeEmail(index)} - > - × - -
    - ); - }} - /> -
    - - {t( - "If you list voters' emails, only them will be able to access the election" - )} - -
    - -
    -
    -
    -
    -
    - - - {check.ok ? ( - -
    - - {t("Validate")} -
    -
    - {t("Confirm your vote")} -
    -
    -
    -
    - {t("Question of the election")} -
    -
    {title}
    -
    - {t("Candidates/Proposals")} -
    -
    -
      - {candidates.map((candidate, i) => { - if (candidate.label !== "") { - return ( -
    • - {candidate.label} -
    • - ); - } else { - return
    • ; - } - })} -
    -
    -
    -
    - {t("Dates")} -
    -
    - {t("The election will take place from")}{" "} - - {start.toLocaleDateString()}, {t("at")}{" "} - {start.toLocaleTimeString()} - {" "} - {t("to")}{" "} - - {finish.toLocaleDateString()}, {t("at")}{" "} - {finish.toLocaleTimeString()} - -
    -
    -
    - {t("Grades")} -
    -
    - {grades.map((mention, i) => { - return i < numGrades ? ( - - {mention.label} - - ) : ( - - ); - })} -
    -
    - {t("Voters' list")} -
    -
    - {electorEmails.length > 0 ? ( - electorEmails.join(", ") - ) : ( -

    - {t("The form contains no address.")} -
    - - {t( - "The election will be opened to anyone with the link" - )} - -

    - )} -
    - {this.state.restrictResult ? ( -
    -
    -
    - - {t("Results available at the close of the vote")} -
    -

    - {electorEmails.length > 0 ? ( - - {t( - "The results page will not be accessible until all participants have voted." - )} - - ) : ( - - {t( - "The results page will not be accessible until the end date is reached." - )}{" "} - ({finish.toLocaleDateString()} {t("at")}{" "} - {finish.toLocaleTimeString()}) - - )} -

    -
    -
    - ) : null} -
    -
    -
    - {t("Start the election")} -
    -
    {t("Cancel")}
    -
    - ) : ( - - )} - -
    -
    -
    - ); - } -} - -export default withTranslation()(withRouter(CreateElection)); diff --git a/src/components/views/CreateSuccess.jsx b/src/components/views/CreateSuccess.jsx deleted file mode 100644 index 542c46c..0000000 --- a/src/components/views/CreateSuccess.jsx +++ /dev/null @@ -1,141 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import { Col, Container, Row } from "reactstrap"; -import { Link } from "react-router-dom"; -import { withTranslation } from "react-i18next"; -import { - faCopy, - faVoteYea, - faExclamationTriangle, - faExternalLinkAlt -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { AppContext } from "../../AppContext"; -import CopyField from "../CopyField"; -import Facebook from "../banner/Facebook"; - -class CreateSuccess extends Component { - static contextType = AppContext; - constructor(props) { - super(props); - const electionSlug = this.props.match.params.slug; - this.state = { - urlOfVote: window.location.origin + "/vote/" + electionSlug, - urlOfResult: window.location.origin + "/result/" + electionSlug - }; - this.urlVoteField = React.createRef(); - this.urlResultField = React.createRef(); - } - - handleClickOnCopyResult = () => { - const input = this.urlResultField.current; - input.focus(); - input.select(); - document.execCommand("copy"); - }; - - render() { - const { t } = this.props; - const electionLink = this.props.invitationOnly ? ( - <> -

    - {t( - "Voters received a link to vote by email. Each link can be used only once!" - )} -

    - - ) : ( - <> -

    {t("Voting address")}

    - - - ); - return ( - - - -

    {t("Successful election creation!")}

    - {this.props.invitationOnly ? null : ( - - )} - -
    - - -
    - - {t("Keep these links carefully")} -
    -
    - {electionLink} - -

    {t("Results address")}

    - -
    - - {/**/} - {/*
    - - -
    */} - -
    - {this.props.invitationOnly ? null : ( - - - - - {t("Participate now!")} - - - - )} -
    - ); - } -} -export default withTranslation()(CreateSuccess); diff --git a/src/components/views/Faq.jsx b/src/components/views/Faq.jsx deleted file mode 100644 index 331d69a..0000000 --- a/src/components/views/Faq.jsx +++ /dev/null @@ -1,280 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import { Col, Container, Row } from "reactstrap"; -import logoLine from "../../logos/logo-line-white.svg"; -import { Link } from "react-router-dom"; -import { AppContext } from "../../AppContext"; -import { withTranslation } from "react-i18next"; -import Paypal from "../banner/Paypal"; - -class Faq extends Component { - static contextType = AppContext; - - constructor(props) { - super(props); - this.state = {}; - } - - render() { - const { t } = this.props; - return ( - - - - logo - - - - -

    {t("FAQ")}

    - -
    - - -

    - Qu’est-ce que le Jugement Majoritaire ? -

    -

    - 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 - »). -

    -
    - -
    - -

    D’où vient le Jugement Majoritaire ?

    -

    - Le jugement majoritaire est un mode de scrutin inventé par deux - chercheurs Français du Centre National de la Recherche - Scientifique (CNRS) en 2011, Michel Balinski et{" "} - Rida Laraki. -

    - -

    - Quels sont les avantages du Jugement Majoritaire ? -

    -

    - Une mesure précise de l’opinion 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 d’informations que dans le cadre du scrutin - uninominal qui, résumant l’opinion des électeurs à un choix, - ignore l’essentiel de l’information quant à ce qu’ils pensent. En - agrégeant un grand nombre d’informations, 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 d’affiner autant que de pacifier la prise de - décision. -

    - -

    - Quand et comment utiliser le Jugement Majoritaire ? -

    -

    - Le Jugement majoritaire s’applique à tout type de votation - collective, qu’il s’agisse d’élire un candidat, de retenir une ou - plusieurs idées lors d’un 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…). -

    - -

    Qui peut utiliser cette application ?

    -

    - Cette application de Jugement Majoritaire est ouverte à toute - personne désireuse de prendre une décision collective, entre amis, - entre collègues, entre membres d’un groupe. Elle est libre d’accès - et gratuite. Notre ambition est de vous proposer la meilleure - expérience de prise de décision collective et démocratique. -

    - -

    - Comment organiser une élection avec plusieurs milliers de votants - ? -

    -

    - Cette application ne convient pas pour les votes à plus de 1000 - votants. Si c’est votre cas, nous vous invitons à nous contacter - par email à l’adresse{" "} - - contact@mieuxvoter.fr - - . Dans le cas d’un 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). -

    - -

    - Je rencontre un problème, comment obtenir de l’aide ? -

    -

    - Si vous rencontrez un problème en utilisant notre application, - prenez contact avec nous par email à l’adresse « - - app@mieuxvoter.fr - - », et prenez soin de bien décrire le problème rencontré dans votre - message. Ajoutez éventuellement dans votre description le lien de - votre vote. -

    - -

    - Y-a t’il une limite de votants appliquée pour les votes sur - invitation ? -

    -

    - 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 à l’adresse « - - contact@mieuxvoter.fr - - ». -

    - -

    - Combien de temps le lien vers la page de résultat reste-t-il actif - ? -

    -

    - Les liens fournis lors de la création de votre vote n’ont pas de - date d’expiration. Conservez-les précieusement afin de pouvoir - consulter les résultat dans le futur. -

    - -

    - Comment puis-je m’assurer qu’une même personne ne vote pas deux - fois? -

    -

    - Dans le cas d’un 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 - d’un lien unique auquel est associé un jeton à usage unique. Ce - jeton est détruit aussitôt que la participation au vote de - l’invité est enregistrée. Il garantit donc à l’organisateur que - chaque participant n’a pu voter qu’une seule fois. -

    - -

    - Dans le cas d’un vote public, toute personne peut participer à - l’élection s’il dispose du lien de l’élection. Il n’y a dans ce - cas aucune limite de soumission d’un vote. Une même personne peut - donc voter plusieurs fois. -

    - -

    - Lorsque j’organise une élection, puis-je connaître le nombre et - l’identité des votants? -

    -

    - Le nombre de votants est indiqué sur la page de résultats de votre - élection. L’identité des votants est quant à elle effacée, afin de - respecter les conditions d’un vote démocratique où l’anonymat - garantit la sincérité des électeurs. -

    - -

    Puis-je modifier mon vote ?

    -

    - 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. -

    - -

    - Comment puis-je récupérer un lien si je l’ai perdu ? -

    -

    - Vous ne pouvez pas récupérer un lien pour voter après qu’il vous - soit communiquer. Gardez le précieusement. Cependant si vous avez - le lien pour voter, nous pouvons vous transmettre le lien des - résultats. -

    - -

    - Comment interpréter les résultats d’un vote au Jugement - Majoritaire ? -

    -

    - 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. -

    - -

    Quelle sécurité pour mes données ?

    -

    - Afin de garantir la sécurité de vos données, leur transmission est - chiffrée et vos votes sont anonymisés. -

    - -

    - Que faites-vous des données collectées ? -

    -

    - L’application app.mieuxvoter.fr a pour seul et unique but de faire - découvrir le vote au Jugement Majoritaire. Elle n’a 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. -

    - -

    Qui est Mieux Voter ?

    -

    - « Mieux Voter » est une association loi 1901 qui promeut - l’utilisation 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 à l’usage de tous. -

    - -

    - Comment nous aider à faire connaître le Jugement Majoritaire ? -

    -

    - Vous avez apprécié votre expérience de vote démocratique au - Jugement Majoritaire ?
    - Nous en sommes ravis ! Vous pouvez nous aider en faisant un don à - l’association ici : -

    - - -
    - {/* - - - {t("Go back to homepage")} - - - */} -
    - ); - } -} - -export default withTranslation()(Faq); diff --git a/src/components/views/Home.jsx b/src/components/views/Home.jsx deleted file mode 100644 index 7c6f61a..0000000 --- a/src/components/views/Home.jsx +++ /dev/null @@ -1,95 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import { withTranslation } from "react-i18next"; -import { Container, Row, Col, Button, Input } from "reactstrap"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faRocket } from "@fortawesome/free-solid-svg-icons"; -import { Redirect } from "react-router-dom"; -import logoLine from "../../logos/logo-line-white.svg"; - -class Home extends Component { - constructor(props) { - super(props); - this.state = { - title: null, - redirect: false - }; - this.focusInput = React.createRef(); - } - - handleSubmit = event => { - event.preventDefault(); - this.setState({ redirect: true }); - }; - - handleChangeTitle = event => { - //console.log(this.context.routesServer.setElection); - this.setState({ title: event.target.value }); - }; - - render() { - const { t } = this.props; - const redirect = this.state.redirect; - - if (redirect) { - return ( - - ); - } - return ( - -
    - - logo - - - -

    - {t( - "Simple and free: organize an election with Majority Judgment." - )} -

    - -
    - - - - - - - - - - -

    {t("No advertising or ad cookies")}

    - -
    -
    -
    - ); - } -} -export default withTranslation()(Home); diff --git a/src/components/views/LegalNotices.jsx b/src/components/views/LegalNotices.jsx deleted file mode 100644 index 825c244..0000000 --- a/src/components/views/LegalNotices.jsx +++ /dev/null @@ -1,77 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import { Col, Container, Row } from "reactstrap"; -import logoLine from "../../logos/logo-line-white.svg"; -import { Link } from "react-router-dom"; -import { AppContext } from "../../AppContext"; -import { withTranslation } from "react-i18next"; - -class LegalNotices extends Component { - static contextType = AppContext; - constructor(props) { - super(props); - this.state = {}; - } - - render() { - const { t } = this.props; - return ( - - - - logo - - - - -

    {t("Legal notices")}

    - -
    - - -

    Editeur

    -

    Cette Application est éditée par l’association loi 1901 - {" "} - “Mieux Voter” - , dont le siège social est situé au 59 rue Saint-André des Arts, à Paris (75006).

    - -

    - Adresse email : - - contact@mieuxvoter.fr - -

    -

    - Directeur de la publication -
    Chloé Ridel -

    -

    Hébergement

    -

    -

      -
    • Base de données : Institut Systèmes Complexes, Paris ;
    • -
    • Fichiers statiques : Netlify, 2325 3rd Street, Suite 215, San Francisco, California 94107.
    • -
    -

    -

    Œuvres graphiques

    -

    - Les illustrations et graphismes sur cette application sont l’œuvre de l’association Mieux Voter. -

    - -
    - - - - {t("Go back to homepage")} - - - -
    - ); - } -} -export default withTranslation()(LegalNotices); diff --git a/src/components/views/PrivacyPolicy.jsx b/src/components/views/PrivacyPolicy.jsx deleted file mode 100644 index 0a30217..0000000 --- a/src/components/views/PrivacyPolicy.jsx +++ /dev/null @@ -1,113 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import { Col, Container, Row } from "reactstrap"; -import logoLine from "../../logos/logo-line-white.svg"; -import { Link } from "react-router-dom"; -import { AppContext } from "../../AppContext"; -import { withTranslation } from "react-i18next"; - -class PrivacyPolicy extends Component { - static contextType = AppContext; - - constructor(props) { - super(props); - this.state = {}; - } - - render() { - const { t } = this.props; - return ( - - - - logo - - - - -

    {t("Privacy policy")}

    - -
    - - -

    - Dernière mise à jour de notre politique de confidentialité - effectuée le 27 avril 2020. -

    -

    Introduction

    -

    - Dans le cadre de la mise à disposition de son application web de - vote au jugement majoritaire, accessible sur Internet à l’adresse - app.mieuxvoter.fr, ci-après l’Application, l’association loi 1901 - « Mieux Voter » , dont le siège social est situé au 59 rue saint - andré des arts, à Paris (75006), ci-après l’Association, 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. -

    -

    Notre politique de confidentialité

    -

    - La présente politique de confidentialité détaille les conditions - d’utilisation et de traitement par l’Association des Données - personnelles (ci-après définies) collectées via l’Application. - L’Association s’engage à respecter les dispositions de la loi - n°78-17 du 6 janvier 1978 relative à l’informatique, 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. -

    -

    Responsable de traitement

    -

    - En qualité de responsable de traitement, l’Association peut - traiter les Données personnelles. -

    -

    - Données personnelles traitées et finalités de traitement -

    -

    - L’Association recueille sur l’Application 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, l’Association traite uniquement les données personnelles - suivantes (définies comme les « Données personnelles ») - strictement nécessaires à la fourniture du service : -

    -
      -
    • Les emails des personnes invitées à un vote
    • -
    - -

    - {" "} - La finalité de traitement de ces données personnelles est de - permettre à l’Association 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. -

    -

    Sécurité des Données personnelles

    -

    - L’Association s’engage, 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. -

    - -
    - {/* - - - {t("Go back to homepage")} - - - */} -
    - ); - } -} - -export default withTranslation()(PrivacyPolicy); diff --git a/src/components/views/Result.jsx b/src/components/views/Result.jsx deleted file mode 100644 index a4bd76b..0000000 --- a/src/components/views/Result.jsx +++ /dev/null @@ -1,419 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import { withTranslation } from "react-i18next"; -import { resolve } from "url"; -import { - Container, - Row, - Col, - Collapse, - Card, - CardHeader, - CardBody, - Table -} from "reactstrap"; -import { i18nGrades } from "../../Util"; -import { AppContext } from "../../AppContext"; -import { errorMessage, Error } from "../../Errors"; -import Facebook from "../banner/Facebook"; - -class Result extends Component { - static contextType = AppContext; - - constructor(props) { - super(props); - this.state = { - candidates: [], - title: null, - numGrades: 0, - colSizeCandidateLg: 4, - colSizeCandidateMd: 6, - colSizeCandidateXs: 12, - colSizeGradeLg: 1, - colSizeGradeMd: 1, - colSizeGradeXs: 1, - collapseGraphics: false, - collapseProfiles: false, - electionGrades: i18nGrades(), - errorMessage: "" - }; - } - - handleErrors = response => { - if (!response.ok) { - response.json().then(response => { - this.setState(() => ({ - errorMessage: errorMessage(response, this.props.t) - })); - }); - throw Error(response); - } - return response; - }; - - resultsToState = response => { - const candidates = response.map(c => ({ - id: c.id, - name: c.name, - profile: c.profile, - grade: c.grade - })); - this.setState(() => ({ candidates: candidates })); - return response; - }; - - detailsToState = response => { - const numGrades = response.num_grades; - const colSizeGradeLg = Math.floor( - (12 - this.state.colSizeCandidateLg) / numGrades - ); - const colSizeGradeMd = Math.floor( - (12 - this.state.colSizeCandidateMd) / numGrades - ); - const colSizeGradeXs = Math.floor( - (12 - this.state.colSizeCandidateXs) / numGrades - ); - this.setState(() => ({ - title: response.title, - numGrades: numGrades, - colSizeGradeLg: colSizeGradeLg, - colSizeGradeMd: colSizeGradeMd, - colSizeGradeXs: colSizeGradeXs, - colSizeCandidateLg: - 12 - colSizeGradeLg * numGrades > 0 - ? 12 - colSizeGradeLg * numGrades - : 12, - colSizeCandidateMd: - 12 - colSizeGradeMd * numGrades > 0 - ? 12 - colSizeGradeMd * numGrades - : 12, - colSizeCandidateXs: - 12 - colSizeGradeXs * numGrades > 0 - ? 12 - colSizeGradeXs * numGrades - : 12, - electionGrades: i18nGrades().slice(0, numGrades) - })); - return response; - }; - - componentDidMount() { - // get details of the election - const electionSlug = this.props.match.params.slug; - if (electionSlug === "dev") { - const dataTest = [ - { - name: "BB", - id: 1, - score: 1.0, - profile: [1, 1, 0, 0, 0, 0, 0], - grade: 1 - }, - { - name: "CC", - id: 2, - score: 1.0, - profile: [0, 0, 2, 0, 0, 0, 0], - grade: 2 - }, - { - name: "AA", - id: 0, - score: 1.0, - profile: [1, 1, 0, 0, 0, 0, 0], - grade: 1 - } - ]; - this.setState({ candidates: dataTest }); - } else { - const detailsEndpoint = resolve( - this.context.urlServer, - this.context.routesServer.getElection.replace( - new RegExp(":slug", "g"), - electionSlug - ) - ); - - fetch(detailsEndpoint) - .then(this.handleErrors) - .then(response => response.json()) - .then(this.detailsToState) - .catch(error => console.log(error)); - - // get results of the election - const resultsEndpoint = resolve( - this.context.urlServer, - this.context.routesServer.getResultsElection.replace( - new RegExp(":slug", "g"), - electionSlug - ) - ); - - fetch(resultsEndpoint) - .then(this.handleErrors) - .then(response => response.json()) - .then(this.resultsToState) - .catch(error => console.log(error)); - } - } - - toggleGraphics = () => { - this.setState(state => ({ collapseGraphics: !state.collapseGraphics })); - }; - - toggleProfiles = () => { - this.setState(state => ({ collapseProfiles: !state.collapseProfiles })); - }; - - render() { - const { errorMessage, candidates, electionGrades } = this.state; - const { t } = this.props; - const i18nGradesObject = i18nGrades(); - const offsetGrade = i18nGradesObject.length - this.state.numGrades; - - if (errorMessage && errorMessage !== "") { - return ; - } - - 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 ( - - - -

    {this.state.title}

    - -
    - - -
      - {candidates.map((candidate, i) => { - const gradeValue = candidate.grade + offsetGrade; - return ( -
    1. - {candidate.name} - - {i18nGradesObject.slice(0).reverse()[gradeValue].label} - - {/* - {(100 * candidate.score).toFixed(1)}% - */} -
    2. - ); - })} -
    -
    - - {t("Number of votes:")} - {" " + numVotes} - -
    - -
    - - - - - -

    - {t("Graph")} -

    -
    - - -
    -
    - - - {candidates.map((candidate, i) => { - return ( - - - {/*candidate.label*/} - - - ); - })} - -
    {i + 1} - - - - {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 ( - - ); - } else { - return null; - } - })} - - -
    -   -
    -
    -
    -
    - - {candidates.map((candidate, i) => { - return ( - - {i > 0 ? ", " : ""} - {i + 1}: {candidate.name} - - ); - })} - -
    -
    - - {electionGrades.map((grade, i) => { - return ( - - {grade.label} - - ); - })} - -
    - - - - - - - - - -

    - {t("Preference profile")} -

    -
    - - -
    - - - - - {electionGrades.map((grade, i) => { - return ( - - ); - })} - - - - {candidates.map((candidate, i) => { - return ( - - - {gradeIds - .slice(0) - .reverse() - .map((id, i) => { - const value = candidate.profile[id]; - const percent = ( - (value / numVotes) * - 100 - ).toFixed(1); - return ; - })} - - ); - })} - -
    # - - {grade.label}{" "} - -
    {i + 1}{percent} %
    -
    - - {candidates.map((candidate, i) => { - return ( - - {i > 0 ? ", " : ""} - {i + 1}: {candidate.name} - - ); - })} - -
    -
    -
    - -
    - - - - - - - ); - } -} - -export default withTranslation()(Result); diff --git a/src/components/views/UnknownElection.jsx b/src/components/views/UnknownElection.jsx deleted file mode 100644 index d76a3b4..0000000 --- a/src/components/views/UnknownElection.jsx +++ /dev/null @@ -1,46 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import { Col, Container, Row } from "reactstrap"; -import { withTranslation } from "react-i18next"; -import logoLine from "../../logos/logo-line-white.svg"; -import { Link } from "react-router-dom"; -import { AppContext } from "../../AppContext"; - -class UnknownElection extends Component { - static contextType = AppContext; - constructor(props) { - super(props); - this.state = {}; - } - - render() { - const { t } = this.props; - return ( - - - - logo - - - - -

    - {t( - "Oops! This election does not exist or it is not available anymore." - )} -

    -

    {t("You can start another election.")}

    - -
    - - - - {t("Go back to homepage")} - - - -
    - ); - } -} -export default withTranslation()(UnknownElection); diff --git a/src/components/views/UnknownView.jsx b/src/components/views/UnknownView.jsx deleted file mode 100644 index 6d8a9b7..0000000 --- a/src/components/views/UnknownView.jsx +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import { Col, Container, Row } from "reactstrap"; -import logoLine from "../../logos/logo-line-white.svg"; -import { Link } from "react-router-dom"; -import { AppContext } from "../../AppContext"; - -class UnknownView extends Component { - static contextType = AppContext; - constructor(props) { - super(props); - this.state = {}; - } - - render() { - return ( - - - - logo - - - - -

    Ooops ! this page doesn't exist !

    - -
    - - - - Go back to homepage - - - -
    - ); - } -} -export default UnknownView; diff --git a/src/components/views/Vote.jsx b/src/components/views/Vote.jsx deleted file mode 100644 index d065f1a..0000000 --- a/src/components/views/Vote.jsx +++ /dev/null @@ -1,355 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import { Redirect } from "react-router-dom"; -import { withTranslation } from "react-i18next"; -import { Button, Col, Container, Row } from "reactstrap"; -import { toast, ToastContainer } from "react-toastify"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faCheck } from "@fortawesome/free-solid-svg-icons"; -import { resolve } from "url"; -import { i18nGrades } from "../../Util"; -import { AppContext } from "../../AppContext"; -import { errorMessage } from "../../Errors"; - -const shuffle = array => array.sort(() => Math.random() - 0.5); - -class Vote extends Component { - static contextType = AppContext; - constructor(props) { - super(props); - this.state = { - candidates: [], - title: null, - numGrades: 0, - ratedCandidates: [], - colSizeCandidateLg: 4, - colSizeCandidateMd: 6, - colSizeCandidateXs: 12, - colSizeGradeLg: 1, - colSizeGradeMd: 1, - colSizeGradeXs: 1, - redirectTo: null, - electionGrades: i18nGrades(), - errorMsg: "" - }; - } - - handleErrors = response => { - if (!response.ok) { - response.json().then(response => { - console.log(response); - const { t } = this.props; - this.setState(() => ({ - errorMsg: errorMessage(response, t) - })); - }); - throw Error(response); - } - return response; - }; - - detailsToState = response => { - const numGrades = response.num_grades; - const candidates = response.candidates.map((c, i) => ({ - id: i, - label: c - })); - shuffle(candidates); - - const colSizeGradeLg = Math.floor( - (12 - this.state.colSizeCandidateLg) / numGrades - ); - const colSizeGradeMd = Math.floor( - (12 - this.state.colSizeCandidateMd) / numGrades - ); - const colSizeGradeXs = Math.floor( - (12 - this.state.colSizeCandidateXs) / numGrades - ); - - this.setState(() => ({ - title: response.title, - candidates: candidates, - numGrades: numGrades, - colSizeGradeLg: colSizeGradeLg, - colSizeGradeMd: colSizeGradeMd, - colSizeGradeXs: colSizeGradeXs, - colSizeCandidateLg: - 12 - colSizeGradeLg * numGrades > 0 - ? 12 - colSizeGradeLg * numGrades - : 12, - colSizeCandidateMd: - 12 - colSizeGradeMd * numGrades > 0 - ? 12 - colSizeGradeMd * numGrades - : 12, - colSizeCandidateXs: - 12 - colSizeGradeXs * numGrades > 0 - ? 12 - colSizeGradeXs * numGrades - : 12 - })); - return response; - }; - - componentDidMount() { - // FIXME we should better handling logs - const electionSlug = this.props.match.params.slug; - const detailsEndpoint = resolve( - this.context.urlServer, - this.context.routesServer.getElection.replace( - new RegExp(":slug", "g"), - electionSlug - ) - ); - fetch(detailsEndpoint) - .then(this.handleErrors) - .then(response => response.json()) - .then(this.detailsToState) - .catch(error => console.log(error)); - } - - handleGradeClick = event => { - let data = { - id: parseInt(event.currentTarget.getAttribute("data-id")), - value: parseInt(event.currentTarget.value) - }; - //remove candidate - let ratedCandidates = this.state.ratedCandidates.filter( - ratedCandidate => ratedCandidate.id !== data.id - ); - ratedCandidates.push(data); - this.setState({ ratedCandidates }); - }; - - handleSubmitWithoutAllRate = () => { - const { t } = this.props; - toast.error(t("You have to judge every candidate/proposal!"), { - position: toast.POSITION.TOP_CENTER - }); - }; - - handleSubmit = event => { - event.preventDefault(); - - const { ratedCandidates } = this.state; - const electionSlug = this.props.match.params.slug; - const token = this.props.location.search.substr(7); - const endpoint = resolve( - this.context.urlServer, - this.context.routesServer.voteElection - ); - - const gradesById = {}; - ratedCandidates.forEach(c => { - gradesById[c.id] = c.value; - }); - const gradesByCandidate = []; - Object.keys(gradesById).forEach(id => { - gradesByCandidate.push(gradesById[id]); - }); - - const payload = { - election: electionSlug, - grades_by_candidate: gradesByCandidate - }; - if (token !== "") { - payload["token"] = token; - } - - fetch(endpoint, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload) - }) - .then(this.handleErrors) - .then(() => - this.setState({ redirectTo: "/vote-success/" + electionSlug }) - ) - .catch(error => error); - }; - - render() { - const { t } = this.props; - const { candidates, errorMsg, redirectTo } = this.state; - const grades = i18nGrades(); - const offsetGrade = grades.length - this.state.numGrades; - const electionGrades = grades.slice(0, this.state.numGrades); - - if (redirectTo) { - return ; - } - - if (errorMsg !== "") { - return ( - - - -

    {errorMsg}

    - -
    -
    - ); - } - - return ( - - -
    - - -

    {this.state.title}

    - -
    - - -
     
    - - {electionGrades.map((grade, gradeId) => { - return gradeId < this.state.numGrades ? ( - - - {grade.label} - - - ) : null; - })} -
    - - {candidates.map((candidate, candidateId) => { - return ( - - -
    {candidate.label}
    -
    - - {electionGrades.map((grade, gradeId) => { - console.assert(gradeId < this.state.numGrades); - const gradeValue = grade.value - offsetGrade; - return ( - - - - ); - })} -
    - ); - })} - - - - {this.state.ratedCandidates.length !== - this.state.candidates.length ? ( - - ) : ( - - )} - - -
    -
    - ); - } -} -export default withTranslation()(Vote); diff --git a/src/components/views/VoteSuccess.jsx b/src/components/views/VoteSuccess.jsx deleted file mode 100644 index d364d57..0000000 --- a/src/components/views/VoteSuccess.jsx +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import { Col, Container, Row } from "reactstrap"; -import { withTranslation } from "react-i18next"; -import logoLine from "../../logos/logo-line-white.svg"; -import { Link } from "react-router-dom"; -import { AppContext } from "../../AppContext"; -import Paypal from "../banner/Paypal"; -import Gform from "../banner/Gform"; - -class VoteSuccess extends Component { - static contextType = AppContext; - render() { - const { t } = this.props; - return ( - - - - logo - - - - -

    {t("Your participation was recorded with success!")}

    -

    {t("Thanks for your participation.")}

    -
    - -
    -
    - -
    - -
    - -
    - ); - } -} -export default withTranslation()(VoteSuccess); diff --git a/src/components/wait/index.jsx b/src/components/wait/index.jsx deleted file mode 100644 index 9765a71..0000000 --- a/src/components/wait/index.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react"; -import Loader from "../loader"; - -const Wait = () => { - return ; -}; - -export default Wait; diff --git a/src/components/wait/loader-pulse-2-alpha.gif b/src/components/wait/loader-pulse-2-alpha.gif deleted file mode 100644 index a2fc0a2..0000000 Binary files a/src/components/wait/loader-pulse-2-alpha.gif and /dev/null differ diff --git a/src/components/wait/loader-pulse-2.gif b/src/components/wait/loader-pulse-2.gif deleted file mode 100644 index e9a4c48..0000000 Binary files a/src/components/wait/loader-pulse-2.gif and /dev/null differ diff --git a/src/errorCode.js b/src/errorCode.js deleted file mode 100644 index f0bca6c..0000000 --- a/src/errorCode.js +++ /dev/null @@ -1,35 +0,0 @@ -/* eslint react/prop-types: 0 */ - -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 redirectError = () => {}; - -export const errorMessage = (error, t) => { - if (error.startsWith(UNKNOWN_ELECTION_ERROR)) { - return t("Oops... The election is unknown."); - } else if (error.startsWith(ONGOING_ELECTION_ERROR)) { - return t( - "The election is still going on. You can't access now to the results." - ); - } else if (error.startsWith(NO_VOTE_ERROR)) { - return t("No votes have been recorded yet. Come back later."); - } else if (error.startsWith(ELECTION_NOT_STARTED_ERROR)) { - return t("The election has not started yet."); - } else if (error.startsWith(ELECTION_FINISHED_ERROR)) { - return t("The election is over. You can't vote anymore"); - } else if (error.startsWith(INVITATION_ONLY_ERROR)) { - return t("You need a token to vote in this election"); - } else if (error.startsWith(USED_TOKEN_ERROR)) { - return t("You seem to have already voted."); - } else if (error.startsWith(WRONG_ELECTION_ERROR)) { - return t("The parameters of the election are incorrect."); - } -}; diff --git a/src/i18n.jsx b/src/i18n.jsx deleted file mode 100644 index 0bc4695..0000000 --- a/src/i18n.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import i18n from "i18next"; -import XHR from "i18next-xhr-backend"; -import LanguageDetector from "i18next-browser-languagedetector"; -import { initReactI18next } from "react-i18next"; - -i18n - .use(XHR) - .use(LanguageDetector) - .use(initReactI18next) // bind react-i18next to the instance - .init({ - fallbackLng: "en", - debug: true, - saveMissing: true, // send not translated keys to endpoint - defaultValue: "__STRING_NOT_TRANSLATED__", - react: { useSuspense: false }, - keySeparator: ">", - nsSeparator: "|", - backend: { - loadPath: "/locale/i18n/{{lng}}/resource.json" - // path to post missing resources - }, - - interpolation: { - escapeValue: false // not needed for react!! - } - - // react i18next special options (optional) - // override if needed - omit if ok with defaults - /* - react: { - bindI18n: 'languageChanged', - bindI18nStore: '', - transEmptyNodeValue: '', - transSupportBasicHtmlNodes: true, - transKeepBasicHtmlNodesFor: ['br', 'strong', 'i'], - useSuspense: true, - } - */ - }); - -export default i18n; diff --git a/src/index.jsx b/src/index.jsx deleted file mode 100644 index 2a9db7a..0000000 --- a/src/index.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import "./scss/config.scss"; -import App from "./App"; -import * as serviceWorker from "./serviceWorker"; - -ReactDOM.render(, document.getElementById("root")); - -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: https://bit.ly/CRA-PWA -serviceWorker.unregister(); diff --git a/src/logos/logo-black.svg b/src/logos/logo-black.svg deleted file mode 100644 index 497c68e..0000000 --- a/src/logos/logo-black.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/src/logos/logo-blue.svg b/src/logos/logo-blue.svg deleted file mode 100644 index 069a92e..0000000 --- a/src/logos/logo-blue.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/src/logos/logo-color.svg b/src/logos/logo-color.svg deleted file mode 100644 index 66da680..0000000 --- a/src/logos/logo-color.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/src/logos/logo-line-black.svg b/src/logos/logo-line-black.svg deleted file mode 100644 index 67804b7..0000000 --- a/src/logos/logo-line-black.svg +++ /dev/null @@ -1,47 +0,0 @@ - - - diff --git a/src/logos/logo-line-blue.svg b/src/logos/logo-line-blue.svg deleted file mode 100644 index f6e9a5c..0000000 --- a/src/logos/logo-line-blue.svg +++ /dev/null @@ -1,47 +0,0 @@ - - - diff --git a/src/logos/logo-line-white.svg b/src/logos/logo-line-white.svg deleted file mode 100644 index ecc8991..0000000 --- a/src/logos/logo-line-white.svg +++ /dev/null @@ -1,47 +0,0 @@ - - - diff --git a/src/logos/logo-white.svg b/src/logos/logo-white.svg deleted file mode 100644 index 1780c87..0000000 --- a/src/logos/logo-white.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/src/scss/_app.scss b/src/scss/_app.scss deleted file mode 100644 index 865b3c1..0000000 --- a/src/scss/_app.scss +++ /dev/null @@ -1,357 +0,0 @@ -// mieux voter vars -$mv-blue-color: #009900 !default; -$mv-red-color: #000099 !default; -$mv-light-color: #efefff !default; -$mv-dark-color: #333 !default; - -// Override default variables before the import bootstrap -$body-bg: #000000 !default; -$body-color: $mv-light-color !default; - -$theme-colors: ( - "primary": $mv-blue-color, - "secondary": $mv-red-color, - "light": $mv-light-color, - "dark": $mv-dark-color, - "danger": #990000, - "success": #009900, - "info": #2b8299, - "warning": #ff6e11 -) !default; - -.logo-text > h1 { - font-size: 16px; - font-weight: bold; - margin: 0; - line-height: 1; -} - -.bold { - font-weight: bold; -} - -.logo-text > h1 > small { - display: block; - letter-spacing: 0.09em; -} -html, -body, -#root, -#root > div { - height: 100%; -} - -main { - background-image: url("/background-mv.png"); - background-size: 100%; - background-attachment: fixed; - background-repeat: no-repeat; - background-color: $mv-blue-color; - min-height: calc(100% - 128px); - overflow: auto; - padding-top: 72px; - padding-bottom: 100px; -} - -header { - position: fixed; - z-index: 10; - width: 100%; -} - -footer { - background-color: $body-bg; - color: $mv-light-color; - padding: 25px; -} - -footer a { - color: $mv-light-color; -} - -footer a:hover { - color: #fff; -} - -hr { - border: none; - border-top: 1px solid $mv-light-color; - width: 100%; - margin: auto; -} - -ul.sortable, -li.sortable { - padding: 0; - margin: 0 0 0 0; - list-style-type: none; -} -li.sortable { - margin: 0 0 15px 0; -} - -.pointer { - cursor: pointer; -} - -.modal { - color: $mv-dark-color; -} - -/* card Vote */ -.cardVote { - background-color: $mv-light-color; - margin: 1em 0; - color: $mv-dark-color; - border-radius: 0.15em; - padding: 1em 0; -} -.cardVote .nowrap { - white-space: nowrap; -} -.cardVote hr { - border-top: 1px solid $mv-dark-color; - margin: 10px 0; -} - -.cardVote.row:hover { - background-color: $mv-light-color-hover; -} - -/* checkbox */ -/* The radio */ -.radio { - display: block; - position: relative; - padding-left: 30px; - margin-bottom: 12px; - cursor: pointer; - font-size: 20px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -/* Hide the browser's default radio button */ -.radio input { - position: absolute; - opacity: 0; - cursor: pointer; -} - -/* Create a custom radio button */ -.checkround { - position: absolute; - top: 6px; - left: 0; - height: 20px; - width: 20px; - background-color: #fff; - border-color: $mv-blue-color; - border-style: solid; - border-width: 2px; - border-radius: 50%; -} - -.checkround.checkround-gray { - border-color: $gray-600; -} - -/* When the radio button is checked, add a blue background */ -.radio input:checked ~ .checkround { - background-color: #fff; -} - -/* Create the indicator (the dot/circle - hidden when not checked) */ -.checkround:after { - content: ""; - position: absolute; - display: none; -} - -/* Show the indicator (dot/circle) when checked */ -.radio input:checked ~ .checkround:after { - display: block; -} - -/* Style the indicator (dot/circle) */ -.radio .checkround:after { - left: 2px; - top: 2px; - width: 12px; - height: 12px; - border-radius: 50%; - background: $mv-blue-color; -} - -/*.radio .checkround.checkround-gray:after { - background: $gray-600; -}*/ - -/* The check */ -.check { - display: block; - position: relative; - padding-left: 25px; - margin-bottom: 12px; - padding-right: 15px; - cursor: pointer; - font-size: 18px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -/* Hide the browser's default checkbox */ -.check input { - position: absolute; - opacity: 0; - cursor: pointer; -} - -/* Create a custom checkbox */ -.checkmark { - position: absolute; - margin-left: calc(50% - 12px); - top: 0; - left: 0; - height: 24px; - width: 24px; - background-color: #fff; - border-color: $mv-blue-color; - border-style: solid; - border-width: 2px; -} - -@include media-breakpoint-down(md) { - .checkmark { - position: absolute; - margin-left: 0; - top: 3px; - } -} - -/* When the checkbox is checked, add a blue background */ -.check input:checked ~ .checkmark { - background-color: #fff; -} - -/* Create the checkmark/indicator (hidden when not checked) */ -.checkmark:after { - content: ""; - position: absolute; - display: none; -} - -/* Show the checkmark when checked */ -.check input:checked ~ .checkmark:after { - display: block; -} - -/* Style the checkmark/indicator */ -.check .checkmark:after { - left: 5px; - top: 1px; - width: 10px; - height: 15px; - border: solid; - border-color: #fff; - border-width: 0 3px 3px 0; - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - transform: rotate(45deg); - cursor: pointer; -} - -.cust-btn { - margin-bottom: 10px; - background-color: $mv-blue-color; - border-width: 2px; - border-color: $mv-blue-color; - color: #fff; -} -.cust-btn:hover { - border-color: $mv-blue-color; - background-color: #fff; - color: $mv-blue-color; - border-radius: 20px; - transform-style: 2s; -} - -/** collapse **/ -.panel-title:after { - content: "+"; - float: right; - font-size: 28px; - font-weight: 900; -} -.panel-title.collapsed:after { - content: "-"; -} - -/** table profiles **/ -.profiles thead, -.profiles tbody, -.profiles tr, -.profiles th, -.profiles td, -.profiles thead th { - border-color: $mv-blue-color; - color: $mv-blue-color; -} - -.median { - border-width: 0 3px 0 0; - border-style: dashed; - border-color: #000; - min-height: 30px; - width: 1px; - position: absolute; - left: 50%; - margin-top: -15px; - margin-left: 13px; -} - -/** react multi email **/ -.react-multi-email > span[data-placeholder] { - padding: 0.25em !important; -} - -/** flag selector **/ -.flag-select > button { - height: 35px; -} - -.flag-select__options { - width: 65px; - text-align: center; - background-color: $mv-light-color !important; -} - -.flag-select__options .flag-select__option { - padding: 0; - margin: 0; -} -.flag-select__options .flag-select__option__icon { - top: 0; -} - -/** result **/ -ol.result > li{ - font-size:1rem; - font-weight:normal; -} -ol.result > li:nth-child(1){ - font-size:1.75rem; - font-weight:bold; -} - -ol.result > li:nth-child(2){ - font-size:1.5rem; -} - -ol.result > li:nth-child(3){ - font-size:1.25rem; -} \ No newline at end of file diff --git a/src/scss/_bootstrap.scss b/src/scss/_bootstrap.scss deleted file mode 100644 index 48fa0ce..0000000 --- a/src/scss/_bootstrap.scss +++ /dev/null @@ -1 +0,0 @@ -@import "~bootstrap/scss/bootstrap.scss"; diff --git a/src/scss/config.scss b/src/scss/config.scss deleted file mode 100644 index 4b3d95c..0000000 --- a/src/scss/config.scss +++ /dev/null @@ -1,24 +0,0 @@ -// mieux voter vars -$mv-blue-color: #2a43a0; -$mv-red-color: #ee455b; -$mv-light-color: #efefff; -$mv-light-color-hover: rgba(#efefff, 0.8); -$mv-dark-color: #333; - -// Override default variables before the import bootstrap -$body-bg: #000000; -$body-color: $mv-light-color; - -$theme-colors: ( - "primary": $mv-blue-color, - "secondary": $mv-red-color, - "light": $mv-light-color, - "dark": $mv-dark-color, - "danger": #990000, - "success": #009900, - "info": #2b8299, - "warning": #ff6e11 -); - -@import "_bootstrap.scss"; -@import "app.scss"; diff --git a/src/serviceWorker.jsx b/src/serviceWorker.jsx deleted file mode 100644 index b1a6e11..0000000 --- a/src/serviceWorker.jsx +++ /dev/null @@ -1,138 +0,0 @@ -// This optional code is used to register a service worker. -// register() is not called by default. - -// This lets the app load faster on subsequent visits in production, and gives -// it offline capabilities. However, it also means that developers (and users) -// will only see deployed updates on subsequent visits to a page, after all the -// existing tabs open on the page have been closed, since previously cached -// resources are updated in the background. - -// To learn more about the benefits of this model and instructions on how to -// opt-in, read https://bit.ly/CRA-PWA - -const isLocalhost = Boolean( - window.location.hostname === "localhost" || - // [::1] is the IPv6 localhost address. - window.location.hostname === "[::1]" || - // 127.0.0.1/8 is considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) -); - -export function register(config) { - // eslint-disable-next-line no-undef - if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { - // The URL constructor is available in all browsers that support SW. - // eslint-disable-next-line no-undef - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; - } - - window.addEventListener("load", () => { - // eslint-disable-next-line no-undef - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); - - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - "This web app is being served cache-first by a service " + - "worker. To learn more, visit https://bit.ly/CRA-PWA" - ); - }); - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, config); - } - }); - } -} - -function registerValidSW(swUrl, config) { - navigator.serviceWorker - .register(swUrl) - .then(registration => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker == null) { - return; - } - installingWorker.onstatechange = () => { - if (installingWorker.state === "installed") { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - "New content is available and will be used when all " + - "tabs for this page are closed. See https://bit.ly/CRA-PWA." - ); - - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log("Content is cached for offline use."); - - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } - } - } - }; - }; - }) - .catch(error => { - console.error("Error during service worker registration:", error); - }); -} - -function checkValidServiceWorker(swUrl, config) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl) - .then(response => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get("content-type"); - if ( - response.status === 404 || - (contentType != null && contentType.indexOf("javascript") === -1) - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } - }) - .catch(() => { - console.log( - "No internet connection found. App is running in offline mode." - ); - }); -} - -export function unregister() { - if ("serviceWorker" in navigator) { - navigator.serviceWorker.ready.then(registration => { - registration.unregister(); - }); - } -}