diff --git a/components/ButtonCopy.tsx b/components/ButtonCopy.tsx index 5557ddb..b395c72 100644 --- a/components/ButtonCopy.tsx +++ b/components/ButtonCopy.tsx @@ -12,14 +12,12 @@ interface ButtonCopyInterface { const ButtonCopy = ({text, content}: ButtonCopyInterface) => { return () diff --git a/components/WaitingBallot.tsx b/components/WaitingBallot.tsx index 13303b1..c448b6f 100644 --- a/components/WaitingBallot.tsx +++ b/components/WaitingBallot.tsx @@ -59,24 +59,26 @@ const InfoElection = ({election, error, display}: InfoElectionInterface) => { :
} - +
+ +
{ }, 3000); const timer3 = setTimeout(() => { - setElection({display: "block"}); + setElection({display: "grid"}); }, 4500); return () => { diff --git a/components/admin/AdminModalEmail.tsx b/components/admin/AdminModalEmail.tsx index 5e12f7c..488f2ba 100644 --- a/components/admin/AdminModalEmail.tsx +++ b/components/admin/AdminModalEmail.tsx @@ -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 (

{t('admin.modal-desc')}

{t('admin.modal-disclaimer')}

- +
+ +
{ @@ -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 ( + @@ -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" diff --git a/components/admin/Order.tsx b/components/admin/Order.tsx new file mode 100644 index 0000000..19d02f9 --- /dev/null +++ b/components/admin/Order.tsx @@ -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 ( + <> + +
+
+

+ {t('admin.order-title')} +

+

+ {t('admin.order-desc')} +

+
+ +
+
+ + ); +}; + +export default Order; diff --git a/components/admin/ParamsField.tsx b/components/admin/ParamsField.tsx index d020973..e633838 100644 --- a/components/admin/ParamsField.tsx +++ b/components/admin/ParamsField.tsx @@ -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}) => { +
diff --git a/components/admin/Private.tsx b/components/admin/Private.tsx index cdf939d..3f04090 100644 --- a/components/admin/Private.tsx +++ b/components/admin/Private.tsx @@ -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'; diff --git a/public/locales/en/resource.json b/public/locales/en/resource.json index 5ecbbde..fc01533 100644 --- a/public/locales/en/resource.json +++ b/public/locales/en/resource.json @@ -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" } diff --git a/public/locales/fr/resource.json b/public/locales/fr/resource.json index c4d0771..c513a20 100644 --- a/public/locales/fr/resource.json +++ b/public/locales/fr/resource.json @@ -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" } diff --git a/services/ElectionContext.tsx b/services/ElectionContext.tsx index 41fde96..00e58ad 100644 --- a/services/ElectionContext.tsx +++ b/services/ElectionContext.tsx @@ -10,10 +10,10 @@ export interface ElectionContextInterface { description: string; candidates: Array; grades: Array; - isRandomOrder: boolean; hideResults: boolean; forceClose: boolean; restricted: boolean; + randomOrder: boolean; endVote: string; emails: Array; } @@ -30,7 +30,7 @@ const defaultElection: ElectionContextInterface = { description: '', candidates: [{...defaultCandidate}, {...defaultCandidate}], grades: [], - isRandomOrder: false, + randomOrder: true, hideResults: true, forceClose: false, restricted: false, diff --git a/services/api.ts b/services/api.ts index 542fd0a..39580c8 100644 --- a/services/api.ts +++ b/services/api.ts @@ -19,11 +19,12 @@ export const createElection = async ( name: string, candidates: Array, grades: Array, - 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, }), }) diff --git a/services/mail.ts b/services/mail.ts index 2ea4ce0..9027127 100644 --- a/services/mail.ts +++ b/services/mail.ts @@ -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; diff --git a/tests/netlify-send-emails.sh b/tests/netlify-send-emails.sh index f1d89d0..97b7191 100755 --- a/tests/netlify-send-emails.sh +++ b/tests/netlify-send-emails.sh @@ -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