feat: add field order

pull/89/head
Pierre-Louis Guhur 1 year ago
parent cf77be13fe
commit c135cdab1b

@ -12,14 +12,12 @@ interface ButtonCopyInterface {
const ButtonCopy = ({text, content}: ButtonCopyInterface) => {
return (<Button
className="bg-white text-black my-2 shadow-lg border-dark border py-3 px-4 border-3 justify-content-between"
className="bg-white text-black my-2 shadow-lg border-dark border py-3 px-4 border-3 justify-content-between gx-2 align-items-end"
onClick={() => navigator.clipboard.writeText(content)}>
<div className="gx-2 align-items-end justify-content-between">
<div>{text}</div>
<div>
<FontAwesomeIcon icon={faCopy} />
</div>
<div>{text}</div>
<div>
<FontAwesomeIcon icon={faCopy} />
</div>
</Button >)

@ -59,24 +59,26 @@ const InfoElection = ({election, error, display}: InfoElectionInterface) => {
</h5>
: <div className="d-grid w-100">
<ButtonCopy
text={t('admin.modal-copy-vote')}
text={t('admin.success-copy-vote')}
content={urlVote}
/>
<ButtonCopy
text={t('admin.modal-copy-vote')}
text={t('admin.success-copy-result')}
content={urlResults}
/>
</div>}
<Button
customIcon={<FontAwesomeIcon icon={faArrowRight} />}
position="right"
color="secondary"
outline={true}
onClick={toggleModal}
className="mt-3 py-3 px-4"
>
{t('admin.go-to-admin')}
</Button>
<div className="d-grid w-100">
<Button
customIcon={<FontAwesomeIcon icon={faArrowRight} />}
position="right"
color="secondary"
outline={true}
onClick={toggleModal}
className="mt-3 py-3 px-4"
>
{t('admin.go-to-admin')}
</Button>
</div>
<Share title={t('common.share-short')} />
<AdminModalEmail
toggle={toggleModal}
@ -139,7 +141,7 @@ export default ({election, error}: WaitingBallotInterface) => {
}, 3000);
const timer3 = setTimeout(() => {
setElection({display: "block"});
setElection({display: "grid"});
}, 4500);
return () => {

@ -6,6 +6,7 @@ import Button from '@components/Button';
import ButtonCopy from '@components/ButtonCopy';
import {sendAdminMail, validateMail} from '@services/mail';
import {getUrlAdmin} from '@services/routes';
import {useElection} from '@services/ElectionContext';
interface AdminModalEmailInterface {
@ -18,8 +19,8 @@ interface AdminModalEmailInterface {
const AdminModalEmail = ({isOpen, toggle, electionId, adminToken}: AdminModalEmailInterface) => {
const {t} = useTranslation();
const [email, setEmail] = useState(undefined);
const election = useElection();
console.log(electionId)
const adminUrl = electionId && adminToken ? getUrlAdmin(electionId.toString(), adminToken) : null;
const handleEmail = (e) => {
@ -27,10 +28,10 @@ const AdminModalEmail = ({isOpen, toggle, electionId, adminToken}: AdminModalEma
}
const handleSubmit = () => {
sendAdminMail(email, adminUrl);
sendAdminMail(email, election.name, adminUrl);
};
const disabled = !email;
const disabled = !email || !validateMail(email);
return (
<Modal
@ -52,7 +53,9 @@ const AdminModalEmail = ({isOpen, toggle, electionId, adminToken}: AdminModalEma
<ModalBody className="p-4">
<p>{t('admin.modal-desc')}</p>
<p className="text-muted">{t('admin.modal-disclaimer')}</p>
<ButtonCopy text={t('admin.modal-copy')} content={adminUrl} />
<div className="d-grid w-100 mb-5">
<ButtonCopy text={t('admin.success-copy-admin')} content={adminUrl} />
</div>
<Form className="container container-fluid">
<div className="mb-3">
<Input

@ -17,11 +17,13 @@ import AccessResults from './AccessResults';
import LimitDate from './LimitDate';
import Grades from './Grades';
import Private from './Private';
import Order from './Order';
import {useElection, ElectionContextInterface} from '@services/ElectionContext';
import {createElection, ElectionPayload} from '@services/api';
import {getUrlVote, getUrlResults} from '@services/routes';
import {GradeItem, CandidateItem} from '@services/type';
import {sendInviteMails} from '@services/mail';
import {gradeColors} from '@services/grades';
const TitleField = () => {
@ -82,6 +84,7 @@ const submitElection = (
election.hideResults,
election.forceClose,
election.restricted,
election.randomOrder,
async (payload: ElectionPayload) => {
const id = payload.id;
const tokens = payload.tokens;
@ -116,6 +119,14 @@ const ConfirmField = ({onSubmit, onSuccess, onFailure, goToCandidates, goToParam
submitElection(election, onSuccess, onFailure);
}
const numCandidates = election.candidates.filter(c => c.active && c.name != "").length;
const numGrades = election.grades.filter(g => g.active && g.name != "").length;
const disabled = (
!election.name || election.name == "" ||
numCandidates < 2 ||
numGrades < 2 || numGrades > gradeColors.length
)
return (
<Container
fluid="xl"
@ -139,6 +150,7 @@ const ConfirmField = ({onSubmit, onSuccess, onFailure, goToCandidates, goToParam
<AccessResults />
<LimitDate />
<Grades />
<Order />
<Private />
</Col>
</Row>
@ -147,6 +159,7 @@ const ConfirmField = ({onSubmit, onSuccess, onFailure, goToCandidates, goToParam
outline={true}
color="secondary"
className="bg-blue"
disabled={disabled}
onClick={handleSubmit}
icon={faArrowRight}
position="right"

@ -0,0 +1,43 @@
/**
* A field to set the order of candidates in the ballot vote.
*/
import {useTranslation} from 'next-i18next';
import {Container} from 'reactstrap';
import Switch from '@components/Switch';
import {useElection, useElectionDispatch} from '@services/ElectionContext';
const Order = () => {
const {t} = useTranslation();
const election = useElection();
const dispatch = useElectionDispatch();
const toggle = () => {
dispatch({
type: 'set',
field: 'randomOrder',
value: !election.randomOrder,
});
};
console.log(election.randomOrder)
return (
<>
<Container className="bg-white p-3 p-md-4 mt-1">
<div className="d-flex">
<div className="me-auto">
<h4 className="mb-0 text-dark">
{t('admin.order-title')}
</h4>
<p className="text-muted d-none d-md-block">
{t('admin.order-desc')}
</p>
</div>
<Switch toggle={toggle} state={election.randomOrder} />
</div>
</Container>
</>
);
};
export default Order;

@ -5,6 +5,7 @@ import Button from '@components/Button';
import Grades from './Grades';
import LimitDate from './LimitDate';
import AccessResults from './AccessResults';
import Order from './Order';
import Private from './Private';
import {useElection} from '@services/ElectionContext';
@ -24,6 +25,7 @@ const ParamsField = ({onSubmit}) => {
<AccessResults />
<LimitDate />
<Grades />
<Order />
<Private />
</div>
<Container className="my-5 d-md-flex d-grid justify-content-md-center">

@ -1,5 +1,5 @@
/**
* A field to update the grades
* A field to set the privacy and add emails
*/
import {useState} from 'react';
import {useTranslation} from 'next-i18next';

@ -73,6 +73,8 @@
"admin.modal-desc": "This link allows you to modify your election. Keep it carefully, or fill out this form to receive a copy by mail.",
"admin.modal-disclaimer": "We do not store any mail. Thus, we will not send you any advertising content.",
"admin.modal-email-placeholder": "Your mail address",
"admin.order-title": "Random order",
"admin.order-desc": "To avoid cognitive bias, we recommend that candidates appear in random order on the ballot.",
"admin.params-submit": "Validate the parameters",
"admin.params-title": "Your parameters",
"admin.access-results": "Immediate access to the results",
@ -96,5 +98,8 @@
"admin.confirm-title": "Confirm your vote",
"admin.success-election": "The vote has been created with success!",
"admin.success-emails": "The voting link has been sent by emails to the participants.",
"admin.success-copy-vote": "Copy the voting link",
"admin.success-copy-result": "Copy the result link",
"admin.success-copy-admin": "Copy the admin link",
"admin.go-to-admin": "Manage the vote"
}

@ -86,6 +86,8 @@
"admin.grades-desc": "Vous pouvez choisir de personaliser le nom et le nombre de mentions. En cas de doute, gardez les mentions par défaut.",
"admin.ending-in": "Dans",
"admin.until": "Jusqu'au",
"admin.order-title": "Ordre aléatoire",
"admin.order-desc": "Pour éviter un biais cognitif, nous recommendons que les candidats apparaissent dans un ordre aléatoire sur le bulletin de vote.",
"admin.private-title": "Vote privé",
"admin.private-desc": "Uniquement les personnes invités par mail pourront participé au vote",
"admin.private-tip": "Vous pouvez copier-coller une liste d'emails depuis un tableur.",
@ -96,5 +98,8 @@
"admin.confirm-title": "Confirmer votre vote",
"admin.success-election": "Le vote a été créé avec succès",
"admin.success-emails": "Le lien du vote a été envoyé par courriel aux participants.",
"admin.success-copy-vote": "Copier le lien du vote",
"admin.success-copy-result": "Copier le lien des résultats",
"admin.success-copy-admin": "Copier le lien d'administration",
"admin.go-to-admin": "Administrez le vote"
}

@ -10,10 +10,10 @@ export interface ElectionContextInterface {
description: string;
candidates: Array<CandidateItem>;
grades: Array<GradeItem>;
isRandomOrder: boolean;
hideResults: boolean;
forceClose: boolean;
restricted: boolean;
randomOrder: boolean;
endVote: string;
emails: Array<string>;
}
@ -30,7 +30,7 @@ const defaultElection: ElectionContextInterface = {
description: '',
candidates: [{...defaultCandidate}, {...defaultCandidate}],
grades: [],
isRandomOrder: false,
randomOrder: true,
hideResults: true,
forceClose: false,
restricted: false,

@ -19,11 +19,12 @@ export const createElection = async (
name: string,
candidates: Array<Candidate>,
grades: Array<Grade>,
description: string = "",
numVoters: number = 0,
hideResults: boolean = true,
forceClose: boolean = false,
restricted: boolean = false,
description: string,
numVoters: number,
hideResults: boolean,
forceClose: boolean,
restricted: boolean,
randomOrder: boolean,
successCallback: Function = null,
failureCallback: Function = console.log
) => {
@ -49,6 +50,7 @@ export const createElection = async (
grades,
hide_results: hideResults,
force_close: forceClose,
random_order: randomOrder,
private: restricted,
}),
})

@ -18,9 +18,9 @@ export const sendInviteMails = async (
throw new Error('The number of emails differ from the number of tokens');
}
const recipientVariables = {};
const recipients = {};
mails.forEach((_, index: number) => {
recipientVariables[mails[index]] = {
recipients[mails[index]] = {
urlVote: urlVotes[index],
urlResult: urlResult,
};
@ -28,14 +28,15 @@ export const sendInviteMails = async (
const locale = getLocaleShort();
const req = await fetch('/.netlify/functions/send-invite-email/', {
const req = await fetch('/.netlify/functions/send-emails', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
recipientVariables,
name,
action: "invite",
recipients,
title: name,
locale,
}),
});
@ -46,7 +47,8 @@ export const sendInviteMails = async (
export const sendAdminMail = async (
mail: string,
adminUrl: URL,
name: string,
urlAdmin: URL,
) => {
/**
* Send an invitation mail using a micro-service with Netlify
@ -55,15 +57,20 @@ export const sendAdminMail = async (
throw new Error('Incorrect format for the email');
}
const req = await fetch('/.netlify/functions/send-admin-email/', {
const req = await fetch('/.netlify/functions/send-emails/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mail,
adminUrl,
}),
action: "admin",
recipients: {
[mail]: {
urlAdmin,
title: name,
},
}
})
});
return req;

@ -28,7 +28,7 @@ send_emails () {
fi
}
send_emails $SCRIPT_DIR/invite-en.json # && \
#send_emails $SCRIPT_DIR/invite-fr.json && \
#send_emails $SCRIPT_DIR/admin-en.json && \
#send_emails $SCRIPT_DIR/admin-fr.json
send_emails $SCRIPT_DIR/invite-en.json && \
send_emails $SCRIPT_DIR/invite-fr.json && \
send_emails $SCRIPT_DIR/admin-en.json && \
send_emails $SCRIPT_DIR/admin-fr.json

Loading…
Cancel
Save