fix: ballot

pull/89/head
Pierre-Louis Guhur 1 year ago
parent fe61b09bb4
commit 114b07b9d9

@ -14,6 +14,8 @@ import {useAppContext} from '@services/context';
import {getUrlResults} from '@services/routes';
import urne from '../public/urne.svg'
import star from '../public/star.svg'
import Logo from './Logo';
import {FORM_FEEDBACK} from '@services/constants';
export interface WaitingBallotInterface {
@ -43,9 +45,9 @@ const ButtonResults = ({election}) => {
const DiscoverMajorityJudgment = () => {
const {t} = useTranslation();
return (
<div className="bg-secondary p-4 text-white">
<div className="bg-secondary h-100 p-4 text-white me-3">
<h5>{t('vote.discover-mj')}</h5>
<h5>{t('vote.discover-mj-desc')}</h5>
<p>{t('vote.discover-mj-desc')}</p>
<a href="https://mieuxvoter.fr/le-jugement-majoritaire">
<div className="d-flex">
<div className="me-2">{t('common.about')}</div>
@ -55,6 +57,40 @@ const DiscoverMajorityJudgment = () => {
</div>)
}
const SupportBetterVote = () => {
const {t} = useTranslation();
return (
<div className="text-secondary h-100 p-4 bg-white">
<div className="d-flex justify-content-between">
<h5>{t('vote.support-better-vote')}</h5>
<Logo title={false} />
</div>
<p>{t('vote.support-desc')}</p>
<a href="https://mieuxvoter.fr/le-jugement-majoritaire">
<div className="d-flex">
<div className="me-2">{t('common.donation')}</div>
<FontAwesomeIcon icon={faArrowRight} />
</div>
</a>
</div>)
}
const Thanks = () => {
const {t} = useTranslation();
return (<>
<h5>{t('vote.thanks')}</h5>
<p>{t('vote.form-desc')}</p>
<a href={FORM_FEEDBACK} target="_blank"
rel="noopener noreferrer"
>
<Button color="secondary" outline={true}>
{t('vote.form')}
</Button>
</a>
</>)
}
interface InfoInterface extends WaitingBallotInterface {
display: string;
}
@ -81,10 +117,12 @@ const Info = ({ballot, error, display}: InfoInterface) => {
</h4>
<ButtonResults election={ballot.election} />
<Container className="justify-content-between">
<Container className="d-flex m-4 justify-content-between">
<DiscoverMajorityJudgment />
<SupportBetterVote />
</Container>
<Thanks />
<Share />
</div >
)
}
@ -154,9 +192,6 @@ export default ({ballot, error}: WaitingBallotInterface) => {
return (<Container
className="d-flex h-100 w-100 align-items-center flex-column"
style={{
maxWidth: "400px"
}}
>
<div
style={{

@ -1,11 +1,6 @@
import {MouseEvent, useState} from 'react'
import {useRouter} from 'next/router';
import {useTranslation} from 'next-i18next';
import Button from '@components/Button';
import {Col, Row, Container} from 'reactstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCalendarDays, faCheck} from '@fortawesome/free-solid-svg-icons';
import {useBallot, BallotTypes, BallotProvider} from '@services/BallotContext';
import {useState} from 'react'
import {Container} from 'reactstrap';
import {useBallot} from '@services/BallotContext';
import CandidateCard from '@components/ballot/CandidateCard'
import TitleBar from '@components/ballot/TitleBar'
import GradeInput from '@components/ballot/GradeInput'
@ -14,34 +9,12 @@ import CandidateModal from '@components/CandidateModalGet';
const BallotDesktop = () => {
const {t} = useTranslation();
const [ballot, dispatch] = useBallot();
const numGrades = ballot.election.grades.length;
const disabled = ballot.votes.length !== ballot.election.candidates.length;
const router = useRouter();
const [candidate, setCandidate] = useState(null);
const handleSubmit = (event: MouseEvent) => {
event.preventDefault();
// const gradesById = {};
// judgments.forEach((c) => {
// gradesById[c.id] = c.value;
// });
// const gradesByCandidate = [];
// Object.keys(gradesById).forEach((id) => {
// gradesByCandidate.push(gradesById[id]);
// });
// castBallot(gradesByCandidate, election.id.toString(), token, () => {
router.push(`/confirm/${ballot.election.id}`);
// });
};
const [candidate, setCandidate] = useState<CandidatePayload | null>(null);
console.log(candidate)
return (
<div className="w-100 h-100 d-none d-md-block">
<TitleBar election={ballot.election} />
@ -66,20 +39,7 @@ const BallotDesktop = () => {
);
})}
<CandidateModal isOpen={candidate !== null} toggle={() => setCandidate(null)} candidate={candidate} />
<Container className="my-5 d-md-flex d-grid justify-content-md-center">
<Button
outline={true}
color="secondary"
className="bg-blue"
onClick={handleSubmit}
disabled={disabled}
icon={faCheck}
position="left"
>
{t('vote.submit')}
</Button>
</Container>
</Container>
</Container >
</div>
)
}

@ -1,13 +1,11 @@
import {MouseEvent, useState} from 'react'
import {useRouter} from 'next/router';
import {useTranslation} from 'next-i18next';
import Button from '@components/Button';
import {Col, Row, Container} from 'reactstrap';
import {useState} from 'react'
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCheck, faChevronLeft, faChevronRight} from '@fortawesome/free-solid-svg-icons';
import {useBallot, BallotTypes, BallotProvider} from '@services/BallotContext';
import {faChevronLeft, faChevronRight} from '@fortawesome/free-solid-svg-icons';
import {useBallot} from '@services/BallotContext';
import CandidateCard from '@components/ballot/CandidateCard'
import GradeInput from '@components/ballot/GradeInput'
import {CandidatePayload} from '@services/api';
import CandidateModal from '@components/CandidateModalGet';
interface TitleInterface {
@ -23,19 +21,12 @@ const TitleName = ({name}: TitleInterface) => {
}
const BallotMobile = () => {
const {t} = useTranslation();
const [ballot, _] = useBallot();
const [offset, setOffset] = useState(0);
const numGrades = ballot.election.grades.length;
const disabled = ballot.votes.length !== ballot.election.candidates.length;
const router = useRouter();
const handleSubmit = (event: MouseEvent) => {
event.preventDefault();
router.push(`/confirm/${ballot.election.id}`);
};
const [candidate, setCandidate] = useState<CandidatePayload | null>(null);
const moveRight = (right: boolean) => {
if (right) setOffset(o => o - 247);
@ -43,8 +34,7 @@ const BallotMobile = () => {
}
return (
<div className="w-100 h-100 d-block d-md-none">
<div className="d-block d-md-none">
<TitleName name={ballot.election.name} />
<div className="w-100 d-flex">
{ballot.election.candidates.map((candidate, candidateId) => {
@ -57,7 +47,10 @@ const BallotMobile = () => {
>
<FontAwesomeIcon color="#0A004C" icon={faChevronLeft} />
</div> : null}
<CandidateCard candidate={candidate} />
<CandidateCard
onClick={() => setCandidate(candidate)}
candidate={candidate}
/>
{candidateId !== ballot.election.candidates.length - 1 ?
<div className="ms-2" onClick={() => moveRight(true)}><FontAwesomeIcon color="#0A004C" icon={faChevronRight} /></div> : null}
@ -77,20 +70,8 @@ const BallotMobile = () => {
</div>
);
})}
<CandidateModal isOpen={candidate !== null} toggle={() => setCandidate(null)} candidate={candidate} />
</div>
<Container className="my-5 d-md-flex d-grid justify-content-md-center">
<Button
outline={true}
color="secondary"
className="bg-blue"
onClick={handleSubmit}
disabled={disabled}
icon={faCheck}
position="left"
>
{t('vote.submit')}
</Button>
</Container>
</div >
)
}

@ -2,10 +2,13 @@ import {useEffect, useState} from 'react';
import Head from 'next/head';
import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
import {useTranslation} from 'next-i18next';
import {getElection, castBallot, ElectionPayload, BallotPayload, ErrorPayload} from '@services/api';
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, castBallot, ElectionPayload, BallotPayload, ErrorPayload} from '@services/api';
import {useBallot, BallotTypes, BallotProvider} from '@services/BallotContext';
import {ENDED_VOTE} from '@services/routes';
import WaitingBallot from '@components/WaitingBallot';
@ -58,6 +61,26 @@ export async function getServerSideProps({query: {pid, tid}, locale}) {
}
const ButtonSubmit = () => {
const {t} = useTranslation();
const [ballot, dispatch] = useBallot();
const disabled = ballot.votes.length !== ballot.election.candidates.length;
return (<Container className="my-5 d-md-flex d-grid justify-content-md-center">
<Button
outline={true}
color="secondary"
className="bg-blue"
role="submit"
disabled={disabled}
icon={faCheck}
position="left"
>
{t('vote.submit')}
</Button>
</Container>
)
}
interface VoteInterface {
@ -85,6 +108,12 @@ 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);
@ -92,7 +121,7 @@ const VoteBallot = ({election, token}: VoteInterface) => {
try {
const res = await castBallot(
ballot.votes,
ballot.election.ref, ballot.election.restricted, token)
ballot.election, ballot.election.restricted, token)
if (res.status !== 200) {
console.error(res);
const msg = await res.json();
@ -108,12 +137,6 @@ const VoteBallot = ({election, token}: VoteInterface) => {
}
};
if (voting) {
return <PatternedBackground>
<WaitingBallot ballot={payload} error={error} />
</PatternedBackground>
}
return (
<form className="w-100 h-100" onSubmit={handleSubmit} autoComplete="off">
<Head>
@ -128,8 +151,11 @@ const VoteBallot = ({election, token}: VoteInterface) => {
</Head>
<Blur />
<BallotDesktop />
<BallotMobile />
<div className="w-100 h-100 d-flex flex-column justify-content-center">
<BallotDesktop />
<BallotMobile />
<ButtonSubmit />
</div>
</form >
);
};

@ -28,6 +28,7 @@
"menu.contact-us": "Contact us",
"common.about": "Read more",
"common.about-mj": "Read more about Better Vote",
"common.donation": "Make a donation",
"common.error": "Oh no... An error has occured...",
"common.better-vote": "Better Vote",
"common.share": "Share the application Better Vote",
@ -107,11 +108,18 @@
"admin.success-copy-result": "Copy the result link",
"admin.success-copy-admin": "Copy the admin link",
"admin.go-to-admin": "Manage the 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",
"vote.form-desc": "Help us to improve the application by clicking below",
"vote.go-to-results": "Show results",
"vote.home-desc": "Participate in the vote and discover majority judgment",
"vote.home-start": "I participate",
"vote.open-until": "Vote open until",
"vote.more-details": "More details...",
"vote.submit": "Cast my ballot",
"vote.success-ballot": "Your vote has successfully been taken into account!"
"vote.success-ballot": "Your vote has successfully been taken into account!",
"vote.support-better-vote": "Support Better Vote",
"vote.support-desc": "Better Vote is a transparent and non-profit association. By joining the association, you contribute to finance its functioning and activities.",
"vote.thanks": "Thanks for your participation!"
}

@ -28,6 +28,7 @@
"menu.contact-us": "Nous contacter",
"common.about": "En savoir plus...",
"common.about-mj": "En savoir plus sur Mieux voter",
"common.donation": "Faire une donation",
"common.error": "Oh non ! Une erreur s'est produite...",
"common.better-vote": "Mieux Voter",
"common.share": "Partagez l'application Mieux voter",
@ -110,10 +111,15 @@
"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",
"vote.form": "Votre avis nous intéresse",
"vote.form-desc": "Aidez nous à améliorer lapplication en cliquant ci-dessous",
"vote.home-desc": "Participez au vote et découvrez le jugement majoritaire.",
"vote.home-start": "Je participe",
"vote.open-until": "Vote ouvert jusqu'au",
"vote.more-details": "Cliquez ici pour en savoir plus",
"vote.submit": "Déposer mon bulletin de vote",
"vote.success-ballot": "Votre vote a bien été pris en compte !"
"vote.success-ballot": "Votre vote a bien été pris en compte !",
"vote.support-better-vote": "Soutenez Mieux Voter",
"vote.support-desc": "Mieux Voter est une association transpartisane et sans but lucratif. En adhérant à lassociation, vous contribuez à financer son fonctionnement et ses activités.",
"vote.thanks": "Merci de votre participation !"
}

@ -132,8 +132,7 @@ export const getElection = async (
export const castBallot = (
votes: Array<Vote>,
electionRef: string,
restricted: boolean,
election: ElectionPayload,
token?: string,
) => {
/**
@ -143,11 +142,14 @@ export const castBallot = (
const endpoint = new URL(api.routesServer.voteElection, api.urlServer);
const payload = {
election_ref: electionRef,
votes: votes,
election_ref: election.ref,
votes: votes.map(v => ({
"candidate_id": election.candidates[v.candidateId].id,
"grade_id": election.grades[v.gradeId].id
}))
};
if (!restricted) {
if (!election.restricted) {
return fetch(endpoint.href, {
method: 'POST',
headers: {'Content-Type': 'application/json'},

@ -7,12 +7,12 @@ export const CONTACT_MAIL = process.env.CONTACT_MAIL || 'app@mieuxvoter.fr';
export const DEFAULT_GRADES = process.env.DEFAULT_GRADES
? process.env.DEFAULT_GRADES.split(',')
: [
'grades.very-good',
'grades.good',
'grades.passable',
'grades.inadequate',
'grades.mediocre',
];
'grades.very-good',
'grades.good',
'grades.passable',
'grades.inadequate',
'grades.mediocre',
];
export const IMGPUSH_URL =
process.env.IMGPUSH_URL || 'https://imgpush.mieuxvoter.fr';
export const GRADE_COLORS = [
@ -24,3 +24,7 @@ export const GRADE_COLORS = [
'#C23D13',
'#F2F0FF',
];
export const FORM_FEEDBACK = process.env.FORM_FEEDBACK || "https://forms.gle/JZ1Mtbz8gt3Fpwnx5";
export const PAYPAL = process.env.FORM_FEEDBACK || "https://www.paypal.com/donate/?hosted_button_id=QD6U4D323WV4S";

Loading…
Cancel
Save