fix: api connection (#72)

* fix: api connection

* fix: confirm page with delayed election

* fix: fr translation
pull/73/head
guhur 3 years ago committed by GitHub
parent 720a9d990d
commit 4c2759d8c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,35 +1,40 @@
import Link from 'next/link' import Link from "next/link";
import {Container, Row, Col} from "reactstrap"; import { Container, Row, Col } from "reactstrap";
import {useTranslation} from "next-i18next"; import { useTranslation } from "next-i18next";
const Error = (props) => {
const Error = props => { const { t } = useTranslation();
const {t} = useTranslation();
return ( return (
<Container> <Container>
<Row> <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-center">
<Link href="/"> <Link href="/">
<a className="btn btn-secondary"> <a className="d-block ml-auto mr-auto mb-4">
{ t("common.backHomepage") } <img src="/logos/logo-line-white.svg" alt="logo" height="128" />
</a> </a>
</Link> </Link>
</Col> </Row>
</Row> <Row className="mt-4">
</Container> <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 export default Error;

@ -40,7 +40,7 @@ const Footer = () => {
className={bboxLink3.top === bboxLink4.top ? "" : "no-tack"} className={bboxLink3.top === bboxLink4.top ? "" : "no-tack"}
> >
<a href="mailto:app@mieuxvoter.fr?subject=[HELP]" style={linkStyle}> <a href="mailto:app@mieuxvoter.fr?subject=[HELP]" style={linkStyle}>
{t("Need help?")} {t("resource.help")}
</a> </a>
</li> </li>
<li <li

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

@ -3,7 +3,11 @@ import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { getDetails } from "@services/api"; import {
getDetails,
apiErrors,
ELECTION_NOT_STARTED_ERROR,
} from "@services/api";
import { Col, Container, Row } from "reactstrap"; import { Col, Container, Row } from "reactstrap";
import Link from "next/link"; import Link from "next/link";
import { import {
@ -15,28 +19,33 @@ import {
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import CopyField from "@components/CopyField"; import CopyField from "@components/CopyField";
import Error from "@components/Error";
import Facebook from "@components/banner/Facebook"; import Facebook from "@components/banner/Facebook";
import config from "../../../next-i18next.config.js"; import config from "../../../next-i18next.config.js";
export async function getServerSideProps({ query: { pid }, locale }) { export async function getServerSideProps({ query: { pid }, locale }) {
const [res, translations] = await Promise.all([ let [details, translations] = await Promise.all([
getDetails( getDetails(pid),
pid,
(res) => ({ ok: true, ...res }),
(err) => ({ ok: false, err })
),
serverSideTranslations(locale, [], config), serverSideTranslations(locale, [], config),
]); ]);
if (!res.ok) { if (details.includes(ELECTION_NOT_STARTED_ERROR)) {
return { props: { err: res.err, ...translations } }; 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 { return {
props: { props: {
invitationOnly: res.on_invitation_only, invitationOnly: details.on_invitation_only,
restrictResults: res.restrict_results, restrictResults: details.restrict_results,
title: res.title, title: details.title,
pid: pid, pid: pid,
...translations, ...translations,
}, },
@ -50,12 +59,12 @@ const ConfirmElection = ({
pid, pid,
err, err,
}) => { }) => {
const { t } = useTranslation();
if (err) { if (err) {
return <Error value={err}></Error>; return <Error value={apiErrors(err, t)} />;
} }
const { t } = useTranslation();
const origin = const origin =
typeof window !== "undefined" && window.location.origin typeof window !== "undefined" && window.location.origin
? window.location.origin ? window.location.origin

@ -24,33 +24,28 @@ export async function getServerSideProps({ query, locale }) {
const { pid, tid } = query; const { pid, tid } = query;
const [res, details, translations] = await Promise.all([ const [res, details, translations] = await Promise.all([
getResults( getResults(pid),
pid, getDetails(pid),
(res) => ({ ok: true, res }),
(err) => {
return { ok: false, err: "Unknown error" };
}
),
getDetails(
pid,
(res) => ({ ok: true, ...res }),
(err) => ({ ok: false, err: "Unknown error" })
),
serverSideTranslations(locale, [], config), serverSideTranslations(locale, [], config),
]); ]);
if (!res.ok) { if (typeof res === "string" || res instanceof String) {
return { props: { err: res.err, ...translations } }; return { props: { err: res.slice(1, -1), ...translations } };
} }
if (!details.ok) {
return { props: { err: details.err, ...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 { return {
props: { props: {
title: details.title, title: details.title,
numGrades: details.num_grades, numGrades: details.num_grades,
candidates: res.res, candidates: res,
pid: pid, pid: pid,
...translations, ...translations,
}, },

@ -15,37 +15,29 @@ import config from "../../../next-i18next.config.js";
const shuffle = (array) => array.sort(() => Math.random() - 0.5); const shuffle = (array) => array.sort(() => Math.random() - 0.5);
export async function getServerSideProps({ query: { pid, tid }, locale }) { export async function getServerSideProps({ query: { pid, tid }, locale }) {
const [res, translations] = await Promise.all([ const [details, translations] = await Promise.all([
getDetails( getDetails(pid),
pid,
(res) => {
console.log("DETAILS:", res);
return { ok: true, ...res };
},
(err) => {
console.log("ERR:", err);
return { ok: false, err: "Unknown error" };
}
),
serverSideTranslations(locale, [], config), serverSideTranslations(locale, [], config),
]); ]);
if (!res.ok) { if (typeof details === "string" || details instanceof String) {
return { props: { err: res.err, ...translations } }; return { props: { err: details, ...translations } };
} }
console.log(res); if (!details.candidates || !Array.isArray(details.candidates)) {
return { props: { err: "Unknown error", ...translations } };
}
shuffle(res.candidates); shuffle(details.candidates);
return { return {
props: { props: {
...translations, ...translations,
invitationOnly: res.on_invitation_only, invitationOnly: details.on_invitation_only,
restrictResults: res.restrict_results, restrictResults: details.restrict_results,
candidates: res.candidates.map((name, i) => ({ id: i, label: name })), candidates: details.candidates.map((name, i) => ({ id: i, label: name })),
title: res.title, title: details.title,
numGrades: res.num_grades, numGrades: details.num_grades,
pid: pid, pid: pid,
token: tid || null, token: tid || null,
}, },
@ -53,8 +45,10 @@ export async function getServerSideProps({ query: { pid, tid }, locale }) {
} }
const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => { const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => {
const { t } = useTranslation();
if (err) { if (err) {
return <Error value={err}></Error>; return <Error value={apiErrors(err, t)}></Error>;
} }
const [judgments, setJudgments] = useState([]); const [judgments, setJudgments] = useState([]);
@ -67,7 +61,6 @@ const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => {
const router = useRouter(); const router = useRouter();
const { t } = useTranslation();
const allGrades = translateGrades(t); const allGrades = translateGrades(t);
const grades = allGrades.filter( const grades = allGrades.filter(
(grade) => grade.value >= allGrades.length - numGrades (grade) => grade.value >= allGrades.length - numGrades

@ -5,38 +5,43 @@ import { useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Paypal from "@components/banner/Paypal"; import Paypal from "@components/banner/Paypal";
import Gform from "@components/banner/Gform"; import Gform from "@components/banner/Gform";
import { getDetails } from "@services/api"; import Error from "@components/Error";
import { getDetails, apiErrors } from "@services/api";
import config from "../../../next-i18next.config.js"; import config from "../../../next-i18next.config.js";
export async function getServerSideProps({ query: { pid }, locale }) { export async function getServerSideProps({ query: { pid }, locale }) {
const [res, translations] = await Promise.all([ const [details, translations] = await Promise.all([
getDetails( getDetails(pid),
pid,
(res) => ({ ok: true, ...res }),
(err) => ({ ok: false, err })
),
serverSideTranslations(locale, [], config), serverSideTranslations(locale, [], config),
]); ]);
if (!res.ok) { if (typeof details === "string" || details instanceof String) {
return { props: { err: res.err, ...translations } }; return { props: { err: res.slice(1, -1), ...translations } };
}
if (!details.candidates || !Array.isArray(details.candidates)) {
return { props: { err: "Unknown error", ...translations } };
} }
return { return {
props: { props: {
...translations, ...translations,
invitationOnly: res.on_invitation_only, invitationOnly: details.on_invitation_only,
restrictResults: res.restrict_results, restrictResults: details.restrict_results,
candidates: res.candidates.map((name, i) => ({ id: i, label: name })), candidates: details.candidates.map((name, i) => ({ id: i, label: name })),
title: res.title, title: details.title,
numGrades: res.num_grades, numGrades: details.num_grades,
pid: pid, pid: pid,
}, },
}; };
} }
const VoteSuccess = ({ title, invitationOnly, pid }) => { const VoteSuccess = ({ title, invitationOnly, pid, err }) => {
const { t } = useTranslation(); const { t } = useTranslation();
if (err && err !== "") {
return <Error value={apiErrors(err, t)} />;
}
return ( return (
<Container> <Container>
<Head> <Head>

@ -6,7 +6,7 @@
"Privacy policy": "Datenschutzerklärung", "Privacy policy": "Datenschutzerklärung",
"resource.legalNotices": "Rechtliche Hinweise", "resource.legalNotices": "Rechtliche Hinweise",
"FAQ": "FAQ", "FAQ": "FAQ",
"Need help?": "Brauchen Sie Hilfe?", "resource.help": "Brauchen Sie Hilfe?",
"BetterVote": " BetterVote", "BetterVote": " BetterVote",
"Voting platform": "Wahlplattform", "Voting platform": "Wahlplattform",
"Majority Judgment": " Mehrheitswahl ", "Majority Judgment": " Mehrheitswahl ",

@ -0,0 +1,11 @@
{
"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"
}

@ -6,7 +6,7 @@
"Privacy policy": "Privacy policy", "Privacy policy": "Privacy policy",
"resource.legalNotices": "Legal notices", "resource.legalNotices": "Legal notices",
"FAQ": "FAQ", "FAQ": "FAQ",
"Need help?": "Need help?", "resource.help": "Need help?",
"BetterVote": "BetterVote", "BetterVote": "BetterVote",
"Voting platform": "Voting platform", "Voting platform": "Voting platform",
"Majority Judgment": "Majority Judgment", "Majority Judgment": "Majority Judgment",

@ -6,7 +6,7 @@
"Privacy policy": "Política de privacidad", "Privacy policy": "Política de privacidad",
"resource.legalNotices": "Avisos legales", "resource.legalNotices": "Avisos legales",
"FAQ": "FAQ", "FAQ": "FAQ",
"Need help?": "¿Necesitas ayuda?", "resource.help": "¿Necesitas ayuda?",
"BetterVote": "VotarMejor", "BetterVote": "VotarMejor",
"Voting platform": "Plataforma de votación", "Voting platform": "Plataforma de votación",
"Majority Judgment": "Juicio Mayoritario", "Majority Judgment": "Juicio Mayoritario",

@ -0,0 +1,11 @@
{
"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."
}

@ -6,7 +6,7 @@
"Privacy policy": "Politique de confidentialité", "Privacy policy": "Politique de confidentialité",
"resource.legalNotices": "Mentions légales", "resource.legalNotices": "Mentions légales",
"FAQ": "FAQ", "FAQ": "FAQ",
"Need help?": "Besoin d'aide ?", "resource.help": "Besoin d'aide ?",
"BetterVote": "MieuxVoter", "BetterVote": "MieuxVoter",
"Voting platform": "Plateforme de vote", "Voting platform": "Plateforme de vote",
"Majority Judgment": "Jugement Majoritaire", "Majority Judgment": "Jugement Majoritaire",
@ -15,7 +15,7 @@
"Delete?": "Supprimer ?", "Delete?": "Supprimer ?",
"Are you sure to delete": "Êtes-vous sûr(e) de supprimer", "Are you sure to delete": "Êtes-vous sûr(e) de supprimer",
"the row": "la ligne", "the row": "la ligne",
"resource.writeQuestion": "Décrire ici votre question ou introduire simplement votre vote (250 caractères max.)", "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.)", "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.", "Please add at least 2 candidates.": "Merci d'ajouter au moins 2 candidats.",
"resource.questionLabel": "Question de votre vote", "resource.questionLabel": "Question de votre vote",

@ -6,7 +6,7 @@
"Privacy policy": "Политика конфиденциальности", "Privacy policy": "Политика конфиденциальности",
"resource.legalNotices": "Официальные уведомления", "resource.legalNotices": "Официальные уведомления",
"FAQ": "Часто задаваемые вопросы", "FAQ": "Часто задаваемые вопросы",
"Need help?": "Нужна помощь?", "resource.help": "Нужна помощь?",
"BetterVote": "BetterVote", "BetterVote": "BetterVote",
"Voting platform": "Платформа голосования", "Voting platform": "Платформа голосования",
"Majority Judgment": "Решение Большинства", "Majority Judgment": "Решение Большинства",

@ -16,7 +16,7 @@ const sendInviteMail = (res) => {
/** /**
* Send an invitation mail using a micro-service with Netlify * Send an invitation mail using a micro-service with Netlify
*/ */
const { title, mails, tokens, locale } = res; const { id, title, mails, tokens, locale } = res;
if (!mails || !mails.length) { if (!mails || !mails.length) {
throw new Error("No emails are provided."); throw new Error("No emails are provided.");
@ -30,14 +30,14 @@ const sendInviteMail = (res) => {
typeof window !== "undefined" && window.location.origin typeof window !== "undefined" && window.location.origin
? window.location.origin ? window.location.origin
: "http://localhost"; : "http://localhost";
const urlVote = (pid) => new URL(`/vote/${pid}`, origin); const urlVote = (pid, token) => new URL(`/vote/${pid}/${token}`, origin);
const urlResult = (pid) => new URL(`/result/${pid}`, origin); const urlResult = (pid) => new URL(`/result/${pid}`, origin);
const recipientVariables = {}; const recipientVariables = {};
tokens.forEach((token, index) => { tokens.forEach((token, index) => {
recipientVariables[mails[index]] = { recipientVariables[mails[index]] = {
urlVote: urlVote(token), urlVote: urlVote(id, token),
urlResult: urlResult(token), urlResult: urlResult(id),
}; };
}); });
@ -146,15 +146,13 @@ const getDetails = (pid, successCallback, failureCallback) => {
return fetch(detailsEndpoint.href) return fetch(detailsEndpoint.href)
.then((response) => { .then((response) => {
if (!response.ok) { if (!response.ok) {
console.log("NOK", response);
return Promise.reject(response.text()); return Promise.reject(response.text());
} }
const res = response.json(); return response.json();
console.log("OK", res);
return res;
}) })
.then(successCallback || ((res) => res)) .then(successCallback || ((res) => res))
.catch(failureCallback || ((err) => err)); .catch(failureCallback || ((err) => err))
.then((res) => res);
}; };
const castBallot = (judgments, pid, token, callbackSuccess, callbackError) => { const castBallot = (judgments, pid, token, callbackSuccess, callbackError) => {
@ -193,30 +191,30 @@ export const WRONG_ELECTION_ERROR = "E9:";
export const apiErrors = (error, t) => { export const apiErrors = (error, t) => {
if (error.includes(UNKNOWN_ELECTION_ERROR)) { if (error.includes(UNKNOWN_ELECTION_ERROR)) {
return t("Oops... The election is unknown."); return t("error.e1");
} }
if (error.includes(ONGOING_ELECTION_ERROR)) { if (error.includes(ONGOING_ELECTION_ERROR)) {
return t( return t("error.e2");
"The election is still going on. You can't access now to the results."
);
} }
if (error.includes(NO_VOTE_ERROR)) { if (error.includes(NO_VOTE_ERROR)) {
return t("No votes have been recorded yet. Come back later."); return t("error.e3");
} }
if (error.includes(ELECTION_NOT_STARTED_ERROR)) { if (error.includes(ELECTION_NOT_STARTED_ERROR)) {
return t("The election has not started yet."); return t("error.e4");
} }
if (error.includes(ELECTION_FINISHED_ERROR)) { if (error.includes(ELECTION_FINISHED_ERROR)) {
return t("The election is over. You can't vote anymore"); return t("error.e5");
} }
if (error.includes(INVITATION_ONLY_ERROR)) { if (error.includes(INVITATION_ONLY_ERROR)) {
return t("You need a token to vote in this election"); return t("error.e6");
} }
if (error.includes(USED_TOKEN_ERROR)) { if (error.includes(USED_TOKEN_ERROR)) {
return t("You seem to have already voted."); return t("error.e7");
} }
if (error.includes(WRONG_ELECTION_ERROR)) { if (error.includes(WRONG_ELECTION_ERROR)) {
return t("The parameters of the election are incorrect."); return t("error.e8");
} else {
return t("error.catch22");
} }
}; };

Loading…
Cancel
Save