Browse Source

fix: api connection (#72)

* fix: api connection

* fix: confirm page with delayed election

* fix: fr translation
pull/73/head
guhur 2 years ago
committed by GitHub
parent
commit
4c2759d8c9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 65
      components/Error.jsx
  2. 2
      components/layouts/Footer.jsx
  3. 10
      next-i18next.config.js
  4. 39
      pages/new/confirm/[pid].jsx
  5. 29
      pages/result/[pid]/[[...tid]].jsx
  6. 39
      pages/vote/[pid]/[[...tid]].jsx
  7. 35
      pages/vote/[pid]/confirm.jsx
  8. 2
      public/locales/de/resource.json
  9. 11
      public/locales/en/error.json
  10. 2
      public/locales/en/resource.json
  11. 2
      public/locales/es/resource.json
  12. 11
      public/locales/fr/error.json
  13. 4
      public/locales/fr/resource.json
  14. 2
      public/locales/ru/resource.json
  15. 36
      services/api.js

65
components/Error.jsx

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

2
components/layouts/Footer.jsx

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

10
next-i18next.config.js

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

39
pages/new/confirm/[pid].jsx

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

29
pages/result/[pid]/[[...tid]].jsx

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

39
pages/vote/[pid]/[[...tid]].jsx

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

35
pages/vote/[pid]/confirm.jsx

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

2
public/locales/de/resource.json

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

11
public/locales/en/error.json

@ -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"
}

2
public/locales/en/resource.json

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

2
public/locales/es/resource.json

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

11
public/locales/fr/error.json

@ -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."
}

4
public/locales/fr/resource.json

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

2
public/locales/ru/resource.json

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

36
services/api.js

@ -16,7 +16,7 @@ const sendInviteMail = (res) => {
/**
* 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) {
throw new Error("No emails are provided.");
@ -30,14 +30,14 @@ const sendInviteMail = (res) => {
typeof window !== "undefined" && window.location.origin
? window.location.origin
: "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 recipientVariables = {};
tokens.forEach((token, index) => {
recipientVariables[mails[index]] = {
urlVote: urlVote(token),
urlResult: urlResult(token),
urlVote: urlVote(id, token),
urlResult: urlResult(id),
};
});
@ -146,15 +146,13 @@ const getDetails = (pid, successCallback, failureCallback) => {
return fetch(detailsEndpoint.href)
.then((response) => {
if (!response.ok) {
console.log("NOK", response);
return Promise.reject(response.text());
}
const res = response.json();
console.log("OK", res);
return res;
return response.json();
})
.then(successCallback || ((res) => res))
.catch(failureCallback || ((err) => err));
.catch(failureCallback || ((err) => err))
.then((res) => res);
};
const castBallot = (judgments, pid, token, callbackSuccess, callbackError) => {
@ -193,30 +191,30 @@ export const WRONG_ELECTION_ERROR = "E9:";
export const apiErrors = (error, t) => {
if (error.includes(UNKNOWN_ELECTION_ERROR)) {
return t("Oops... The election is unknown.");
return t("error.e1");
}
if (error.includes(ONGOING_ELECTION_ERROR)) {
return t(
"The election is still going on. You can't access now to the results."
);
return t("error.e2");
}
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)) {
return t("The election has not started yet.");
return t("error.e4");
}
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)) {
return t("You need a token to vote in this election");
return t("error.e6");
}
if (error.includes(USED_TOKEN_ERROR)) {
return t("You seem to have already voted.");
return t("error.e7");
}
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