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 {CandidatePayload} from '@services/api';
import CandidateModal from '@components/CandidateModalGet';
import {useTranslation} from 'next-i18next';
const BallotDesktop = ({hasVoted}) => {
const {t} = useTranslation();
const BallotDesktop = () => {
const [ballot, dispatch] = useBallot();
const numGrades = ballot.election.grades.length;
const disabled = ballot.votes.length !== ballot.election.candidates.length;
@ -22,6 +25,9 @@ const BallotDesktop = () => {
style={{maxWidth: '1050px'}}
>
<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) => {
return (
<div

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

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

@ -185,6 +185,7 @@
"result.share": "Share results",
"success.election-closed": "The vote has been created 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-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",

@ -186,6 +186,7 @@
"result.how-to-interpret": "Comment interpréter les résultats",
"success.election-closed": "L'élection est désormais clôturée.",
"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-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",

@ -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 = (
votes: Array<Vote>,
election: ElectionPayload,

Loading…
Cancel
Save