diff --git a/functions/send-invite-email/invite-en.html b/functions/send-invite-email/invite-en.html new file mode 100644 index 0000000..a399098 --- /dev/null +++ b/functions/send-invite-email/invite-en.html @@ -0,0 +1,218 @@ + + + + + + + + + + + + +
+ {{#i18n 'email.happy' }}We are happy to send you this email! You will be able to vote using majority judgment.{{/i18n}} +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + Logo + +
+
+ + + + +
+

{{#i18n 'email.hello'}}Hi, there! 🙂{{/i18n}}

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ {{#i18n 'email.happy'}}We are happy to send you this email! You will be able to vote using majority judgment.{{/i18n}} +

+
+

+ {{#i18n 'email.why'}}This email was sent to you because your email address was entered to participate in the vote on the subject:{{/i18n}} +   + {{title}} +

+
+ + + + +
+ + + + +
+ + {{#i18n 'common.vote' }}Vote!{{/i18n}}
+
+
+

+ {{#i18n 'email.copyLink' }}If that doesn't work, copy and paste the following link into your browser:{{/i18n}} +   + %recipient.urlVote% +

+
+

+ {{#i18n 'email.linkResult' }}The results will be available with the following link when the vote is finished:{{/i18n}} +   + %recipient.urlResult% +

+
+

{{#i18n 'email.bye'}}Good vote{{/i18n}},
{{#i18n 'common.mieuxvoter'}}Mieux Voter{{/i18n}}

+
+
+ + + + + +
+

+ + {{#i18n 'email.aboutjm'}}Need any further information?{{/i18n}} + +

+

+ + {{#i18n 'common.helpus'}}Do you want to help us?{{/i18n}} + +

+
+
+ + +
+ + + + + + + +
+

+ {{#i18n email.why }}You received this email because someone invited you to vote.{{/i18n}} +

+
+

{{#i18n mieuxvoter }}Mieux Voter{{/i18n}} - app@mieuxvoter.fr

+
+
+ + diff --git a/functions/send-invite-email/invite-en.txt b/functions/send-invite-email/invite-en.txt new file mode 100644 index 0000000..f4b8f60 --- /dev/null +++ b/functions/send-invite-email/invite-en.txt @@ -0,0 +1,19 @@ +{{i18n 'email.hello'}}Hi there! 🙂{{i18n}} + +{{i18n 'email.happy'}}We are happy to send you this email! You will be able to vote using majority judgment.{{i18n}} + +{{i18n 'email.why'}}This email was sent to you because your email was filled out to participate in the vote on the subject:{{i18n}} + +{{ title }} + +{{i18n 'email.linkVote' }}The link for the vote is as follows:{{i18n}} + +%recipient.urlVote% + +{{i18n 'email.linkResult' }}The link that will give you the results when they are available is as follows:{{i18n}} + +%recipient.urlResult% + +{{i18n 'email.bye'}}Good vote{{i18n}} + +{{i18n 'common.mieuxvoter'}}Mieux Voter{{i18n}} diff --git a/functions/send-invite-email/invite-fr.html b/functions/send-invite-email/invite-fr.html new file mode 100644 index 0000000..d0f614b --- /dev/null +++ b/functions/send-invite-email/invite-fr.html @@ -0,0 +1,203 @@ + + + + + + + + + + + + +
Nous sommes très heureux de vous partager ce lien de vote ! Vous allez pouvoir voter avec le jugement majoritaire.
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + Logo + +
+
+ + + + +
+

Bonjour ! 🙂

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+

Nous sommes très heureux de vous partager ce lien de vote ! Vous allez pouvoir voter avec le jugement majoritaire.

+
+

Vous avez été invité·e à participer à l'élection suivante :

+
+ + + + +
+ + + + +
+ Voter !
+
+
+

+ Si le lien ne fonctionne pas, vous pouvez le copier et le coller dans la barre de navigation de votre navigateur. +   + %recipient.urlVote% +

+
+

+ A la fin de l'Ă©lection, vous pourrez accĂ©der aux rĂ©sultats en cliquant sur ce lien : +   + %recipient.urlResult% +

+
+

Bon vote,
Mieux Voter

+
+
+ + + + + +
+

+ Besoin de plus d'information +

+

+ Vous souhaitez nous aider ? +

+
+
+ + +
+ + + + + + + +
+

Vous avez été invité·e à participer à l'élection suivante

+
+

Mieux Voter - app@mieuxvoter.fr

+
+
+ + diff --git a/functions/send-invite-email/invite-fr.txt b/functions/send-invite-email/invite-fr.txt new file mode 100644 index 0000000..9ec0f3e --- /dev/null +++ b/functions/send-invite-email/invite-fr.txt @@ -0,0 +1,17 @@ +Bonjour ! 🙂 + +Vous avez été invité·e à participer à l'élection suivante : + +{{ title }} + +Le lien pour voter est le suivant : + +%recipient.urlVote% + +A la fin de l'élection, vous pourrez accéder aux résultats en cliquant sur ce lien : + +%recipient.urlResult% + +Bon vote ! 🤗 + +Mieux Voter diff --git a/functions/send-invite-email/invite.html b/functions/send-invite-email/invite.html index 14b78e8..a399098 100644 --- a/functions/send-invite-email/invite.html +++ b/functions/send-invite-email/invite.html @@ -130,7 +130,9 @@ - +
{{#i18n 'common.vote' }}Vote!{{/i18n}} + + {{#i18n 'common.vote' }}Vote!{{/i18n}}
@@ -144,7 +146,7 @@

{{#i18n 'email.copyLink' }}If that doesn't work, copy and paste the following link into your browser:{{/i18n}}   - {{invitation_url}} + %recipient.urlVote%

@@ -152,9 +154,9 @@

- {{#i18n email.linkResult }}The results will be available with the following link when the vote is finished:{{/i18n}} + {{#i18n 'email.linkResult' }}The results will be available with the following link when the vote is finished:{{/i18n}}   - {{result_url}} + %recipient.urlResult%

diff --git a/functions/send-invite-email/send-invite-email.js b/functions/send-invite-email/send-invite-email.js index 39fc630..6c4321b 100755 --- a/functions/send-invite-email/send-invite-email.js +++ b/functions/send-invite-email/send-invite-email.js @@ -34,57 +34,72 @@ const err = { }; // setup i18n -i18next.use(Backend).init({ - lng: "en", - ns: ["emailInvite", "common"], - defaultNS: "emailInvite", - fallbackNS: "common", - debug: false, - fallbackLng: ["en", "fr"], - backend: { - backends: [FSBackend, HttpApi], - backendOptions: [{ loadPath: "/public/locales/{{lng}}/{{ns}}.json" }, {}], - }, -}); +// i18next.use(Backend).init({ +// lng: "fr", +// ns: ["emailInvite", "common"], +// defaultNS: "emailInvite", +// fallbackNS: "common", +// debug: false, +// fallbackLng: ["fr"], +// backend: { +// backends: [FSBackend, HttpApi], +// backendOptions: [{ loadPath: "/public/locales/{{lng}}/{{ns}}.json" }, {}], +// }, +// }); // setup the template engine // See https://github.com/UUDigitalHumanitieslab/handlebars-i18next -function extend(target, ...sources) { - sources.forEach((source) => { - if (source) - for (let key in source) { - target[key] = source[key]; - } - }); - return target; -} -Handlebars.registerHelper("i18n", function (key, { hash, data, fn }) { - let parsed = {}; - const jsonKeys = [ - "lngs", - "fallbackLng", - "ns", - "postProcess", - "interpolation", - ]; - jsonKeys.forEach((key) => { - if (hash[key]) { - parsed[key] = JSON.parse(hash[key]); - delete hash[key]; - } - }); - let options = extend({}, data.root.i18next, hash, parsed, { - returnObjects: false, - }); - let replace = (options.replace = extend({}, this, options.replace, hash)); - delete replace.i18next; // may creep in if this === data.root - if (fn) options.defaultValue = fn(replace); - return new Handlebars.SafeString(i18next.t(key, options)); -}); -const txtStr = fs.readFileSync(__dirname + "/invite.txt").toString(); -const txtTemplate = Handlebars.compile(txtStr); -const htmlStr = fs.readFileSync(__dirname + "/invite.html").toString(); -const htmlTemplate = Handlebars.compile(htmlStr); +// function extend(target, ...sources) { +// sources.forEach((source) => { +// if (source) +// for (let key in source) { +// target[key] = source[key]; +// } +// }); +// return target; +// } +// Handlebars.registerHelper("i18n", function (key, { hash, data, fn }) { +// let parsed = {}; +// const jsonKeys = [ +// "lngs", +// "fallbackLng", +// "ns", +// "postProcess", +// "interpolation", +// ]; +// jsonKeys.forEach((key) => { +// if (hash[key]) { +// parsed[key] = JSON.parse(hash[key]); +// delete hash[key]; +// } +// }); +// let options = extend({}, data.root.i18next, hash, parsed, { +// returnObjects: false, +// }); +// let replace = (options.replace = extend({}, this, options.replace, hash)); +// delete replace.i18next; // may creep in if this === data.root +// if (fn) options.defaultValue = fn(replace); +// return new Handlebars.SafeString(i18next.t(key, options)); +// }); +// const txtStr = fs.readFileSync(__dirname + "/invite.txt").toString(); +const txtStr = { + en: fs.readFileSync(__dirname + "/invite-en.txt").toString(), + fr: fs.readFileSync(__dirname + "/invite-fr.txt").toString(), +}; +const txtTemplate = { + en: Handlebars.compile(txtStr.en), + fr: Handlebars.compile(txtStr.fr), +}; +const htmlStr = { + en: fs.readFileSync(__dirname + "/invite-en.html").toString(), + fr: fs.readFileSync(__dirname + "/invite-fr.html").toString(), +}; +const htmlTemplate = { + en: Handlebars.compile(htmlStr.en), + fr: Handlebars.compile(htmlStr.fr), +}; + +const test = Handlebars.compile("test"); const sendMail = async (event) => { if (event.httpMethod !== "POST") { @@ -96,23 +111,24 @@ const sendMail = async (event) => { } const data = JSON.parse(event.body); - if (!data.recipientVariables || !data.title) { + if (!data.recipientVariables || !data.title || !data.locale) { return { statusCode: 422, body: "Recipient variables and title are required.", }; } - i18next.changeLanguage(data.locale || "en"); + // i18next.changeLanguage(data.locale); const templateData = { title: data.title, }; const mailgunData = { - from: `${i18next.t("Mieux Voter")} `, + // from: `${i18next.t("Mieux Voter")} `, + from: '"Mieux Voter" ', to: Object.keys(data.recipientVariables), - text: txtTemplate(templateData), - html: htmlTemplate(templateData), + text: txtTemplate.fr(templateData), + html: htmlTemplate.fr(templateData), subject: data.title, "h:Reply-To": "app@mieuxvoter.fr", "recipient-variables": JSON.stringify(data.recipientVariables), @@ -121,7 +137,6 @@ const sendMail = async (event) => { const res = mg.messages .create("mg.app.mieuxvoter.fr", mailgunData) .then((msg) => { - console.log(msg); return success; }) // logs response data .catch((err) => { diff --git a/package-lock.json b/package-lock.json index 4266a8b..417cfea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2054,6 +2054,11 @@ "supports-color": "^5.3.0" } }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, "chokidar": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", @@ -2164,6 +2169,22 @@ } } }, + "cookies": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", + "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "requires": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, "core-js": { "version": "3.12.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.12.1.tgz", @@ -2214,6 +2235,11 @@ "yaml": "^1.10.0" } }, + "crc-32": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-0.2.2.tgz", + "integrity": "sha1-9EBWigxqRfDuu7V8M7FWAV9PBLY=" + }, "create-ecdh": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", @@ -2263,6 +2289,11 @@ "node-fetch": "2.6.1" } }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + }, "crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", @@ -2783,6 +2814,11 @@ } } }, + "handlebars-i18next": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/handlebars-i18next/-/handlebars-i18next-1.0.1.tgz", + "integrity": "sha512-m5sxMthNYFXDYkj7r1MhSiW4tqfIfEKYlUn4TtdXSJT+r6YA9zQddd01BGOgDj3TmjJQc6bDiUQgQVEkluaSdg==" + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -2899,6 +2935,11 @@ } } }, + "i18next-client": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/i18next-client/-/i18next-client-1.10.3.tgz", + "integrity": "sha1-dtA1NVftkNHnqHdU1QBNP3gB/ek=" + }, "i18next-fs-backend": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-1.1.1.tgz", @@ -2920,6 +2961,34 @@ "@babel/runtime": "^7.4.5" } }, + "i18next-text": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/i18next-text/-/i18next-text-0.5.6.tgz", + "integrity": "sha1-nxPB5DKmoOYaRNtYINeS/IizRmI=", + "requires": { + "crc-32": "^0.2.2", + "i18next": "^1.7.10", + "md5": "^2.0.0", + "sha1": "^1.1.0" + }, + "dependencies": { + "i18next": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-1.10.6.tgz", + "integrity": "sha1-/d2LSRUCxIlnpiljvHIv+JfN3qA=", + "requires": { + "cookies": ">= 0.2.2", + "i18next-client": "1.10.3", + "json5": "^0.2.0" + } + }, + "json5": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.2.0.tgz", + "integrity": "sha1-ttcDXHDEVw+IPH7cdZ3jrgPbM0M=" + } + } + }, "iconv-lite": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", @@ -2989,6 +3058,11 @@ "call-bind": "^1.0.0" } }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "is-callable": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", @@ -3158,6 +3232,14 @@ "minimist": "^1.2.0" } }, + "keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "requires": { + "tsscmp": "1.0.6" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -3257,6 +3339,16 @@ "semver": "^6.0.0" } }, + "md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "requires": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -4208,6 +4300,15 @@ "safe-buffer": "^5.0.1" } }, + "sha1": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", + "integrity": "sha1-rdqnqTFo85PxnrKxUJFhjicA+Eg=", + "requires": { + "charenc": ">= 0.0.1", + "crypt": ">= 0.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -4445,6 +4546,11 @@ "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==" }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" + }, "tty-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", diff --git a/package.json b/package.json index 75b4e1b..00cfff8 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,13 @@ "dotenv": "^8.6.0", "form-data": "^4.0.0", "handlebars": "^4.7.7", + "handlebars-i18next": "^1.0.1", "i18next": "^20.2.2", "i18next-chained-backend": "^2.1.0", "i18next-fs-backend": "^1.1.1", "i18next-http-backend": "^1.2.4", "i18next-localstorage-backend": "^3.1.2", + "i18next-text": "^0.5.6", "mailgun.js": "^3.3.2", "next": "^10.2.0", "next-i18next": "^8.2.0", diff --git a/pages/new/confirm/[pid].jsx b/pages/new/confirm/[pid].jsx index daf7582..d33439b 100644 --- a/pages/new/confirm/[pid].jsx +++ b/pages/new/confirm/[pid].jsx @@ -147,7 +147,7 @@ const ConfirmElection = ({ {participate} - + {t("resource.resultsBtn")} diff --git a/pages/new/index.js b/pages/new/index.js index 05daa1c..9ccce37 100644 --- a/pages/new/index.js +++ b/pages/new/index.js @@ -165,7 +165,7 @@ const CreateElection = (props) => { title, candidates.map((c) => c.label).filter((c) => c !== ""), { - emails, + mails: emails, numGrades, start: start.getTime() / 1000, finish: finish.getTime() / 1000, diff --git a/services/api.js b/services/api.js index 975a8a6..8f77576 100644 --- a/services/api.js +++ b/services/api.js @@ -18,6 +18,10 @@ const sendInviteMail = (res) => { */ const { title, mails, tokens, locale } = res; + if (!mails || !mails.length) { + throw new Error("No emails are provided."); + } + if (mails.length !== tokens.length) { throw new Error("The number of emails differ from the number of tokens"); } @@ -49,12 +53,7 @@ const sendInviteMail = (res) => { }), }); - return Promise.all([ - new Promise((resolve, reject) => { - resolve(res); - }), - req, - ]); + return req.then((any) => res); }; const createElection = ( @@ -64,7 +63,7 @@ const createElection = ( /** * Create an election from its title, its candidates and a bunch of options */ - emails, + mails, numGrades, start, finish, @@ -77,6 +76,7 @@ const createElection = ( const endpoint = new URL(api.routesServer.setElection, api.urlServer); console.log(endpoint.href); + const onInvitationOnly = mails && mails.length > 0; fetch(endpoint.href, { method: "POST", @@ -86,9 +86,9 @@ const createElection = ( body: JSON.stringify({ title, candidates, - on_invitation_only: emails.length > 0, + on_invitation_only: onInvitationOnly, num_grades: numGrades, - elector_emails: emails || [], + elector_emails: mails || [], start_at: start, finish_at: finish, select_language: locale || "en", @@ -103,8 +103,12 @@ const createElection = ( } return response.json(); }) - .then((res) => sendInviteMail({ locale, mails: emails || [], ...res })) - .then((res) => res[0]) // remove response from mail invitations + .then((res) => { + if (onInvitationOnly) { + return sendInviteMail({ locale, mails: mails, ...res }); + } + return res; + }) .then(successCallback) .catch(failureCallback || console.log); };