feat: add msg if already voted

pull/100/head
Pierre-Louis Guhur 1 year ago
parent 85cbba76da
commit b4c18df548

@ -6,8 +6,11 @@ import TitleBar from '@components/ballot/TitleBar';
import GradeInput from '@components/ballot/GradeInput'; import GradeInput from '@components/ballot/GradeInput';
import {CandidatePayload} from '@services/api'; import {CandidatePayload} from '@services/api';
import CandidateModal from '@components/CandidateModalGet'; import CandidateModal from '@components/CandidateModalGet';
import {useTranslation} from 'next-i18next';
const BallotDesktop = ({hasVoted}) => {
const {t} = useTranslation();
const BallotDesktop = () => {
const [ballot, dispatch] = useBallot(); const [ballot, dispatch] = useBallot();
const numGrades = ballot.election.grades.length; const numGrades = ballot.election.grades.length;
const disabled = ballot.votes.length !== ballot.election.candidates.length; const disabled = ballot.votes.length !== ballot.election.candidates.length;
@ -22,6 +25,9 @@ const BallotDesktop = () => {
style={{maxWidth: '1050px'}} style={{maxWidth: '1050px'}}
> >
<h1 className="mb-5">{ballot.election.name}</h1> <h1 className="mb-5">{ballot.election.name}</h1>
{hasVoted && <h5 className="d-flex text-start">
{t("vote.already-voted")}
</h5>}
{ballot.election.candidates.map((candidate, candidateId) => { {ballot.election.candidates.map((candidate, candidateId) => {
return ( return (
<div <div

@ -6,6 +6,7 @@ import CandidateCard from '@components/ballot/CandidateCard'
import GradeInput from '@components/ballot/GradeInput' import GradeInput from '@components/ballot/GradeInput'
import {CandidatePayload} from '@services/api'; import {CandidatePayload} from '@services/api';
import CandidateModal from '@components/CandidateModalGet'; import CandidateModal from '@components/CandidateModalGet';
import {useTranslation} from 'next-i18next';
interface TitleInterface { interface TitleInterface {
@ -20,7 +21,9 @@ const TitleName = ({name}: TitleInterface) => {
) )
} }
const BallotMobile = () => { const BallotMobile = ({hasVoted}) => {
const {t} = useTranslation();
const [ballot, _] = useBallot(); const [ballot, _] = useBallot();
const [offset, setOffset] = useState(0); const [offset, setOffset] = useState(0);
@ -36,6 +39,9 @@ const BallotMobile = () => {
return ( return (
<div className="d-block d-md-none"> <div className="d-block d-md-none">
<TitleName name={ballot.election.name} /> <TitleName name={ballot.election.name} />
{hasVoted && <h6 className="d-flex text-start p-4">
{t("vote.already-voted")}
</h6>}
<div className="w-100 d-flex"> <div className="w-100 d-flex">
{ballot.election.candidates.map((candidate, candidateId) => { {ballot.election.candidates.map((candidate, candidateId) => {
return ( return (

@ -1,15 +1,16 @@
import { useEffect, useState } from 'react'; import {useEffect, useState} from 'react';
import Head from 'next/head'; import Head from 'next/head';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
import { useTranslation } from 'next-i18next'; import {useTranslation} from 'next-i18next';
import { Container } from 'reactstrap'; import {Container} from 'reactstrap';
import { faCheck } from '@fortawesome/free-solid-svg-icons'; import {faCheck} from '@fortawesome/free-solid-svg-icons';
import BallotDesktop from '@components/ballot/BallotDesktop'; import BallotDesktop from '@components/ballot/BallotDesktop';
import Button from '@components/Button'; import Button from '@components/Button';
import BallotMobile from '@components/ballot/BallotMobile'; import BallotMobile from '@components/ballot/BallotMobile';
import Blur from '@components/Blur'; import Blur from '@components/Blur';
import { import {
getElection, getElection,
getBallot,
castBallot, castBallot,
ElectionPayload, ElectionPayload,
BallotPayload, BallotPayload,
@ -20,26 +21,27 @@ import {
BallotTypes, BallotTypes,
BallotProvider, BallotProvider,
} from '@services/BallotContext'; } from '@services/BallotContext';
import { getUrl, RouteTypes } from '@services/routes'; import {getUrl, RouteTypes} from '@services/routes';
import { isEnded } from '@services/utils'; import {isEnded} from '@services/utils';
import WaitingBallot from '@components/WaitingBallot'; import WaitingBallot from '@components/WaitingBallot';
import PatternedBackground from '@components/PatternedBackground'; import PatternedBackground from '@components/PatternedBackground';
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}) {
if (!pid) { if (!pid) {
return { notFound: true }; return {notFound: true};
} }
const electionRef = pid.replaceAll('-', ''); const electionRef = pid.replaceAll('-', '');
const [election, translations] = await Promise.all([ const [election, ballot, translations] = await Promise.all([
getElection(electionRef), getElection(electionRef),
tid ? getBallot(tid) : new Response(null),
serverSideTranslations(locale, ['resource']), serverSideTranslations(locale, ['resource']),
]); ]);
if ('msg' in election) { if ('msg' in election) {
return { notFound: true }; return {notFound: true};
} }
if (isEnded(election.date_end)) { if (isEnded(election.date_end)) {
@ -56,7 +58,7 @@ export async function getServerSideProps({ query: { pid, tid }, locale }) {
!election.candidates || !election.candidates ||
!Array.isArray(election.candidates) !Array.isArray(election.candidates)
) { ) {
return { notFound: true }; return {notFound: true};
} }
const description = JSON.parse(election.description); const description = JSON.parse(election.description);
@ -70,12 +72,13 @@ export async function getServerSideProps({ query: { pid, tid }, locale }) {
...translations, ...translations,
election, election,
token: tid || null, token: tid || null,
previousBallot: ballot,
}, },
}; };
} }
const ButtonSubmit = () => { const ButtonSubmit = () => {
const { t } = useTranslation(); const {t} = useTranslation();
const [ballot, dispatch] = useBallot(); const [ballot, dispatch] = useBallot();
const disabled = ballot.votes.length !== ballot.election.candidates.length; const disabled = ballot.votes.length !== ballot.election.candidates.length;
@ -100,9 +103,10 @@ interface VoteInterface {
election: ElectionPayload; election: ElectionPayload;
err: string; err: string;
token?: string; token?: string;
previousBallot: BallotPayload
} }
const VoteBallot = ({ election, token }: VoteInterface) => { const VoteBallot = ({election, token, previousBallot}: VoteInterface) => {
const { t } = useTranslation(); const {t} = useTranslation();
const [ballot, dispatch] = useBallot(); const [ballot, dispatch] = useBallot();
@ -121,14 +125,6 @@ const VoteBallot = ({ election, token }: VoteInterface) => {
return <div>"Loading..."</div>; return <div>"Loading..."</div>;
} }
if (voting) {
return (
<PatternedBackground>
<WaitingBallot ballot={payload} error={error} />
</PatternedBackground>
);
}
const handleSubmit = async (event) => { const handleSubmit = async (event) => {
event.preventDefault(); event.preventDefault();
setVoting(true); setVoting(true);
@ -149,6 +145,14 @@ const VoteBallot = ({ election, token }: VoteInterface) => {
} }
}; };
if (voting) {
return (
<PatternedBackground>
<WaitingBallot ballot={payload} error={error} />
</PatternedBackground>
);
}
return ( return (
<form <form
className="w-100 flex-fill d-flex align-items-center" className="w-100 flex-fill d-flex align-items-center"
@ -168,8 +172,8 @@ const VoteBallot = ({ election, token }: VoteInterface) => {
<Blur /> <Blur />
<div className="w-100 h-100 d-flex flex-column justify-content-center"> <div className="w-100 h-100 d-flex flex-column justify-content-center">
<BallotDesktop /> <BallotDesktop hasVoted={previousBallot != null} />
<BallotMobile /> <BallotMobile hasVoted={previousBallot != null} />
<ButtonSubmit /> <ButtonSubmit />
</div> </div>
</form> </form>

@ -185,6 +185,7 @@
"result.share": "Share results", "result.share": "Share results",
"success.election-closed": "The vote has been created with success!", "success.election-closed": "The vote has been created with success!",
"success.election-updated": "The vote has been updated with success!", "success.election-updated": "The vote has been updated with success!",
"vote.already-voted": "You have already voted, but you can change your vote.",
"vote.discover-mj": "Discover majority judgment", "vote.discover-mj": "Discover majority judgment",
"vote.discover-mj-desc": "Developed by French researchers, majority judgment is a voting system that improves voter expressiveness and provides the best consensus.", "vote.discover-mj-desc": "Developed by French researchers, majority judgment is a voting system that improves voter expressiveness and provides the best consensus.",
"vote.form": "Your opinion is important to us", "vote.form": "Your opinion is important to us",

@ -186,6 +186,7 @@
"result.how-to-interpret": "Comment interpréter les résultats", "result.how-to-interpret": "Comment interpréter les résultats",
"success.election-closed": "L'élection est désormais clôturée.", "success.election-closed": "L'élection est désormais clôturée.",
"success.election-updated": "L'élection a été mise à jour.", "success.election-updated": "L'élection a été mise à jour.",
"vote.already-voted": "Vous avez déjà voté, mais vous pouvez modifier votre vote.",
"vote.discover-mj": "Découvrez le jugement majoritaire", "vote.discover-mj": "Découvrez le jugement majoritaire",
"vote.discover-mj-desc": "Créé par des chercheurs français, le jugement majoritaire est un mode de scrutin qui améliore l'expressivité des électeurs et fournit le meilleur consensus.", "vote.discover-mj-desc": "Créé par des chercheurs français, le jugement majoritaire est un mode de scrutin qui améliore l'expressivité des électeurs et fournit le meilleur consensus.",
"vote.go-to-results": "Voir les résultats", "vote.go-to-results": "Voir les résultats",

@ -248,6 +248,40 @@ export const getElection = async (
} }
}; };
/**
* Fetch a ballot from the API. Return null if the ballot does not exist.
*/
export const getBallot = async (
token: string
): Promise<ElectionPayload | HTTPPayload> => {
const path = api.routesServer.voteElection
const endpoint = new URL(path, URL_SERVER);
if (!token) {
throw new Error('Missing token');
}
try {
const response = await fetch(endpoint.href, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.status != 200) {
console.log("STATUS", await response.text())
return null;
}
const payload = await response.json();
return {...payload, status: response.status};
} catch (error) {
return {status: 400, msg: 'Unknown API error'};
}
};
export const castBallot = ( export const castBallot = (
votes: Array<Vote>, votes: Array<Vote>,
election: ElectionPayload, election: ElectionPayload,

Loading…
Cancel
Save