fix: emailing

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

@ -53,7 +53,7 @@ const InfoElection = ({election, error, display}: InfoElectionInterface) => {
{t('admin.success-election')} {t('admin.success-election')}
</h4> </h4>
{election && election.private ? {election && election.restricted ?
<h5 className="text-center"> <h5 className="text-center">
{t('admin.success-emails')} {t('admin.success-emails')}
</h5> </h5>

@ -28,7 +28,9 @@ const AdminModalEmail = ({isOpen, toggle, electionId, adminToken}: AdminModalEma
} }
const handleSubmit = () => { const handleSubmit = () => {
console.log(election);
sendAdminMail(email, election.name, adminUrl); sendAdminMail(email, election.name, adminUrl);
toggle();
}; };
const disabled = !email || !validateMail(email); const disabled = !email || !validateMail(email);
@ -52,10 +54,11 @@ const AdminModalEmail = ({isOpen, toggle, electionId, adminToken}: AdminModalEma
<ModalBody className="p-4"> <ModalBody className="p-4">
<p>{t('admin.modal-desc')}</p> <p>{t('admin.modal-desc')}</p>
<p className="text-muted">{t('admin.modal-disclaimer')}</p>
<div className="d-grid w-100 mb-5"> <div className="d-grid w-100 mb-5">
<ButtonCopy text={t('admin.success-copy-admin')} content={adminUrl} /> <ButtonCopy text={t('admin.success-copy-admin')} content={adminUrl} />
</div> </div>
<p>{t('admin.modal-email')}</p>
<p className="text-muted">{t('admin.modal-disclaimer')}</p>
<Form className="container container-fluid"> <Form className="container container-fluid">
<div className="mb-3"> <div className="mb-3">
<Input <Input

@ -1,4 +1,4 @@
import {useState, useEffect, createRef} from 'react'; import {useState, useEffect, useRef, KeyboardEvent} from 'react';
import {useTranslation} from 'next-i18next'; import {useTranslation} from 'next-i18next';
import {Container} from 'reactstrap'; import {Container} from 'reactstrap';
import {faArrowRight} from '@fortawesome/free-solid-svg-icons'; import {faArrowRight} from '@fortawesome/free-solid-svg-icons';
@ -10,6 +10,7 @@ import CandidateField from './CandidateField';
const CandidatesField = ({onSubmit}) => { const CandidatesField = ({onSubmit}) => {
const {t} = useTranslation(); const {t} = useTranslation();
const submitReference = useRef(null);
const election = useElection(); const election = useElection();
const dispatch = useElectionDispatch(); const dispatch = useElectionDispatch();
@ -28,6 +29,20 @@ const CandidatesField = ({onSubmit}) => {
} }
}, [candidates]); }, [candidates]);
useEffect(() => {
if (!disabled && submitReference.current) {
submitReference.current.focus();
}
}, [disabled, submitReference]);
const handleKeyPress = (e: KeyboardEvent<HTMLInputElement>) => {
console.log(e.key)
if (e.key == "Enter" && !disabled) {
onSubmit();
}
}
return ( return (
<Container className="candidate flex-grow-1 my-5 flex-column d-flex justify-content-between"> <Container className="candidate flex-grow-1 my-5 flex-column d-flex justify-content-between">
<div className="d-flex flex-column"> <div className="d-flex flex-column">
@ -51,10 +66,12 @@ const CandidatesField = ({onSubmit}) => {
outline={true} outline={true}
color="secondary" color="secondary"
className="bg-blue" className="bg-blue"
ref={submitReference}
onClick={onSubmit} onClick={onSubmit}
disabled={disabled} disabled={disabled}
icon={faArrowRight} icon={faArrowRight}
position="right" position="right"
onKeyPress={handleKeyPress}
> >
{t('admin.candidates-submit')} {t('admin.candidates-submit')}
</Button> </Button>

@ -1,5 +1,6 @@
import {useState} from 'react' import {useState} from 'react'
import {useTranslation} from 'next-i18next'; import {useTranslation} from 'next-i18next';
import {NextRouter, useRouter} from 'next/router';
import { import {
faPen, faPen,
faArrowRight, faArrowRight,
@ -71,6 +72,7 @@ const submitElection = (
election: ElectionContextInterface, election: ElectionContextInterface,
successCallback: Function, successCallback: Function,
failureCallback: Function, failureCallback: Function,
router: NextRouter,
) => { ) => {
const candidates = election.candidates.filter(c => c.active).map((c: CandidateItem) => ({name: c.name, description: c.description, image: c.image})) const candidates = election.candidates.filter(c => c.active).map((c: CandidateItem) => ({name: c.name, description: c.description, image: c.image}))
const grades = election.grades.filter(c => c.active).map((g: GradeItem, i: number) => ({name: g.name, value: i})) const grades = election.grades.filter(c => c.active).map((g: GradeItem, i: number) => ({name: g.name, value: i}))
@ -87,18 +89,19 @@ const submitElection = (
election.randomOrder, election.randomOrder,
async (payload: ElectionPayload) => { async (payload: ElectionPayload) => {
const id = payload.id; const id = payload.id;
const tokens = payload.tokens; const tokens = payload.invites;
if (typeof election.emails !== 'undefined' && election.emails.length > 0) { if (typeof election.emails !== 'undefined' && election.emails.length > 0) {
if (typeof payload.tokens === 'undefined' || payload.tokens.length === election.emails.length) { if (typeof payload.invites === 'undefined' || payload.invites.length !== election.emails.length) {
throw Error('Can not send invite emails'); throw Error('Can not send invite emails');
} }
const urlVotes = payload.tokens.map((token: string) => getUrlVote(id.toString(), token)); const urlVotes = payload.invites.map((token: string) => getUrlVote(id.toString(), token));
const urlResult = getUrlResults(id.toString()); const urlResult = getUrlResults(id.toString());
await sendInviteMails( await sendInviteMails(
election.emails, election.emails,
election.name, election.name,
urlVotes, urlVotes,
urlResult, urlResult,
router,
); );
} }
successCallback(payload); successCallback(payload);
@ -111,12 +114,13 @@ const submitElection = (
const ConfirmField = ({onSubmit, onSuccess, onFailure, goToCandidates, goToParams}) => { const ConfirmField = ({onSubmit, onSuccess, onFailure, goToCandidates, goToParams}) => {
const {t} = useTranslation(); const {t} = useTranslation();
const election = useElection(); const election = useElection();
const router = useRouter();
const handleSubmit = () => { const handleSubmit = () => {
onSubmit(); onSubmit();
submitElection(election, onSuccess, onFailure); submitElection(election, onSuccess, onFailure, router);
} }
const numCandidates = election.candidates.filter(c => c.active && c.name != "").length; const numCandidates = election.candidates.filter(c => c.active && c.name != "").length;

@ -5,7 +5,7 @@ import {getLocaleShort} from '@services/utils';
const LanguageSelector = (props) => { const LanguageSelector = (props) => {
const router = useRouter(); const router = useRouter();
const localeShort = getLocaleShort(); const localeShort = getLocaleShort(router);
const selectHandler = (e) => { const selectHandler = (e) => {
let locale = e.toLowerCase(); let locale = e.toLowerCase();

@ -15,7 +15,6 @@ const {
FROM_EMAIL_ADDRESS, FROM_EMAIL_ADDRESS,
REPLY_TO_EMAIL_ADDRESS, REPLY_TO_EMAIL_ADDRESS,
} = process.env; } = process.env;
console.log("MAILGUN URL", MAILGUN_URL)
const mailgun = new Mailgun(formData); const mailgun = new Mailgun(formData);
const mg = mailgun.client({ const mg = mailgun.client({
@ -25,7 +24,6 @@ const mg = mailgun.client({
}); });
console.log("I18N config", i18n)
i18next.init(i18n, (err, t) => { i18next.init(i18n, (err, t) => {
if (err) return console.log('something went wrong loading', err); if (err) return console.log('something went wrong loading', err);
t("foo"); t("foo");
@ -34,8 +32,6 @@ i18next.init(i18n, (err, t) => {
Handlebars.registerHelper('i18n', Handlebars.registerHelper('i18n',
(str: string): string => { (str: string): string => {
console.log("I18N", str)
console.log("I18Next", i18next, i18next.t)
return (i18next != undefined ? i18next.t(str) : str); return (i18next != undefined ? i18next.t(str) : str);
} }
); );
@ -60,6 +56,7 @@ const handler: Handler = async (event) => {
}; };
} }
console.log("EVENT BODY", event.body)
const {recipients, action, locale} = JSON.parse(event.body) as RequestPayload; const {recipients, action, locale} = JSON.parse(event.body) as RequestPayload;
if (!recipients) { if (!recipients) {

@ -70,11 +70,12 @@
"admin.candidates-submit": "Validate the candidates", "admin.candidates-submit": "Validate the candidates",
"admin.candidates-back-step": "Back to candidates", "admin.candidates-back-step": "Back to candidates",
"admin.modal-title": "Managing an election", "admin.modal-title": "Managing an election",
"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-desc": "This link allows you to modify your election. Keep it carefully, as it will not be provided to you again.",
"admin.modal-email": "To receive a copy of this link by email, fill out this form.",
"admin.modal-disclaimer": "We do not store any mail. Thus, we will not send you any advertising content.", "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.modal-email-placeholder": "Your mail address",
"admin.order-title": "Random order", "admin.order-title": "Random order",
"admin.order-desc": "To avoid cognitive bias, we recommend that candidates appear in random order on the ballot.", "admin.order-desc": "To avoid any cognitive bias, we recommend that candidates appear in random order on the ballot.",
"admin.params-submit": "Validate the parameters", "admin.params-submit": "Validate the parameters",
"admin.params-title": "Your parameters", "admin.params-title": "Your parameters",
"admin.access-results": "Immediate access to the results", "admin.access-results": "Immediate access to the results",

@ -76,8 +76,9 @@
"admin.limit-duration": "Limiter la durée du vote", "admin.limit-duration": "Limiter la durée du vote",
"admin.limit-duration-desc": "", "admin.limit-duration-desc": "",
"admin.modal-title": "Administration du vote", "admin.modal-title": "Administration du vote",
"admin.modal-desc": "Ce lien vous permet de modifier votre vote. Conservez le précieusement, ou remplissez votre adresse email ci-dessous pour en recevoir une copie", "admin.modal-desc": "Ce lien vous permet de modifier votre vote. Conservez le précieusement, ca il ne vous sera pas transmis une seconde fois.",
"admin.modal-disclaimer": "Nous ne stockons aucun email. Nous ne vous enverrons donc aucun contenu publicitaire.", "admin.modal-email": "Pour recevoir une copie par courriel, indiquez nous votre adresse courrielle",
"admin.modal-disclaimer": "Nous ne stockons aucune adresse courrielle. Nous ne vous enverrons donc aucun contenu publicitaire.",
"admin.modal-email-placeholder": "Votre adresse email", "admin.modal-email-placeholder": "Votre adresse email",
"admin.photo": "Photo", "admin.photo": "Photo",
"admin.optional": "facultatif", "admin.optional": "facultatif",

@ -34,7 +34,7 @@ export const createElection = async (
const endpoint = new URL(api.routesServer.setElection, api.urlServer); const endpoint = new URL(api.routesServer.setElection, api.urlServer);
if (!restricted && numVoters > 0) { if (!restricted && numVoters > 0) {
throw Error("Set the election as not private!"); throw Error("Set the election as not restricted!");
} }
try { try {
@ -45,13 +45,16 @@ export const createElection = async (
}, },
body: JSON.stringify({ body: JSON.stringify({
name, name,
description, description: JSON.stringify({
description: description,
randomOrder: randomOrder
}),
candidates, candidates,
grades, grades,
num_voters: numVoters,
hide_results: hideResults, hide_results: hideResults,
force_close: forceClose, force_close: forceClose,
random_order: randomOrder, restricted,
private: restricted,
}), }),
}) })
if (req.ok && req.status === 200) { if (req.ok && req.status === 200) {
@ -227,11 +230,11 @@ export interface ElectionPayload {
date_end: string; date_end: string;
hide_results: boolean; hide_results: boolean;
force_close: boolean; force_close: boolean;
private: boolean; restricted: boolean;
id: number; id: number;
grades: Array<GradePayload>; grades: Array<GradePayload>;
candidates: Array<CandidatePayload>; candidates: Array<CandidatePayload>;
tokens: Array<string>; invites: Array<string>;
admin: string; admin: string;
} }

@ -1,3 +1,4 @@
import {NextRouter} from 'next/router';
import {getLocaleShort} from './utils'; import {getLocaleShort} from './utils';
export const sendInviteMails = async ( export const sendInviteMails = async (
@ -5,6 +6,7 @@ export const sendInviteMails = async (
name: string, name: string,
urlVotes: Array<string | URL>, urlVotes: Array<string | URL>,
urlResult: string | URL, urlResult: string | URL,
router: NextRouter,
) => { ) => {
/** /**
@ -26,7 +28,7 @@ export const sendInviteMails = async (
}; };
}); });
const locale = getLocaleShort(); const locale = getLocaleShort(router);
const req = await fetch('/.netlify/functions/send-emails', { const req = await fetch('/.netlify/functions/send-emails', {
method: 'POST', method: 'POST',
@ -57,7 +59,7 @@ export const sendAdminMail = async (
throw new Error('Incorrect format for the email'); throw new Error('Incorrect format for the email');
} }
const req = await fetch('/.netlify/functions/send-emails/', { const req = await fetch('/.netlify/functions/send-emails', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

@ -2,11 +2,9 @@
* This file contains several utils functions * This file contains several utils functions
*/ */
import {useRouter} from 'next/router'; import {NextRouter} from 'next/router';
export const getLocaleShort = (): string => {
const router = useRouter();
export const getLocaleShort = (router: NextRouter): string => {
if (!router.locale) { if (!router.locale) {
return router.defaultLocale.substring(0, 2).toUpperCase(); return router.defaultLocale.substring(0, 2).toUpperCase();
} }

@ -2,13 +2,15 @@
# This file tests the netlify function for sending emails # This file tests the netlify function for sending emails
# Check if the port is already used or not # Check if the port is already used or not
is_using=$(lsof -i:9999 | awk -F ' ' '{ print $1 }') port=${1:-9999}
is_using=$(lsof -i:$port | awk -F ' ' '{ print $1 }')
if [ -z "$is_using" ]; then if [ -z "$is_using" ]; then
echo "Starting a server on port 9999"; echo "Starting a server on port $port";
netlify functions:serve --port 9999 & netlify functions:serve --port $port &
elif ! [[ "$is_using" =~ .*"node".* ]]; then elif ! [[ "$is_using" =~ .*"node".* ]]; then
echo "$is_using" echo "$is_using"
echo "The port 9999 is already used and not by us :-(" echo "The port $port is already used and not by us :-("
exit 1; exit 1;
else else
echo "The server is running." echo "The server is running."
@ -18,7 +20,7 @@ fi
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
send_emails () { send_emails () {
res=$(netlify functions:invoke --port 9999 send-emails --payload "$(cat $1)") res=$(netlify functions:invoke --port $port send-emails --payload "$(cat $1)")
echo "$res" echo "$res"
# status=$(echo "$res" | head -n 1 | cut -d ',' -f 1 | cut -d ':' -f 2) # status=$(echo "$res" | head -n 1 | cut -d ',' -f 1 | cut -d ':' -f 2)
status=$(echo "$res" | jq '.["status"]') status=$(echo "$res" | jq '.["status"]')

Loading…
Cancel
Save