From 90bd9e795816bf91b4b748570680070e2aadb8c8 Mon Sep 17 00:00:00 2001 From: Sebastian Mihalache <sebastian.mihalache@gmail.con> Date: Tue, 25 Sep 2018 11:02:19 +0300 Subject: [PATCH] feat(email-templating): add files and parameters --- .../component-email-templating/.gitignore | 8 + packages/component-email-templating/README.md | 64 +++++++ .../config/default.js | 18 ++ .../component-email-templating/config/test.js | 3 + packages/component-email-templating/index.js | 1 + .../component-email-templating/package.json | 33 ++++ .../src/EmailTemplate.js | 96 +++++++++++ .../component-email-templating/src/helpers.js | 53 ++++++ .../src/templates/invitation.html | 5 + .../src/templates/notification.html | 3 + .../src/templates/partials/body.hbs | 25 +++ .../src/templates/partials/button.hbs | 19 ++ .../src/templates/partials/footer.hbs | 36 ++++ .../src/templates/partials/header.hbs | 163 ++++++++++++++++++ .../src/templates/partials/intro.hbs | 1 + .../src/templates/partials/invButtons.hbs | 88 ++++++++++ .../src/templates/partials/invHeader.hbs | 161 +++++++++++++++++ .../templates/partials/invLowerContent.hbs | 20 +++ .../templates/partials/invManuscriptData.hbs | 15 ++ .../templates/partials/invUpperContent.hbs | 13 ++ .../src/templates/partials/signature.hbs | 4 + .../tests/helpers.test.js | 60 +++++++ .../src/routes/emails/helpers.js | 8 +- .../src/routes/emails/notifications.js | 4 +- .../app/config/journal/metadata.js | 6 + packages/xpub-faraday/config/components.json | 1 + packages/xpub-faraday/config/default.js | 3 + 27 files changed, 905 insertions(+), 6 deletions(-) create mode 100644 packages/component-email-templating/.gitignore create mode 100644 packages/component-email-templating/README.md create mode 100644 packages/component-email-templating/config/default.js create mode 100644 packages/component-email-templating/config/test.js create mode 100644 packages/component-email-templating/index.js create mode 100644 packages/component-email-templating/package.json create mode 100644 packages/component-email-templating/src/EmailTemplate.js create mode 100644 packages/component-email-templating/src/helpers.js create mode 100644 packages/component-email-templating/src/templates/invitation.html create mode 100644 packages/component-email-templating/src/templates/notification.html create mode 100644 packages/component-email-templating/src/templates/partials/body.hbs create mode 100644 packages/component-email-templating/src/templates/partials/button.hbs create mode 100644 packages/component-email-templating/src/templates/partials/footer.hbs create mode 100644 packages/component-email-templating/src/templates/partials/header.hbs create mode 100644 packages/component-email-templating/src/templates/partials/intro.hbs create mode 100644 packages/component-email-templating/src/templates/partials/invButtons.hbs create mode 100644 packages/component-email-templating/src/templates/partials/invHeader.hbs create mode 100644 packages/component-email-templating/src/templates/partials/invLowerContent.hbs create mode 100644 packages/component-email-templating/src/templates/partials/invManuscriptData.hbs create mode 100644 packages/component-email-templating/src/templates/partials/invUpperContent.hbs create mode 100644 packages/component-email-templating/src/templates/partials/signature.hbs create mode 100644 packages/component-email-templating/tests/helpers.test.js diff --git a/packages/component-email-templating/.gitignore b/packages/component-email-templating/.gitignore new file mode 100644 index 000000000..3614a8100 --- /dev/null +++ b/packages/component-email-templating/.gitignore @@ -0,0 +1,8 @@ +_build/ +api/ +logs/ +node_modules/ +uploads/ +.env.* +.env +config/local*.* \ No newline at end of file diff --git a/packages/component-email-templating/README.md b/packages/component-email-templating/README.md new file mode 100644 index 000000000..a163de5cb --- /dev/null +++ b/packages/component-email-templating/README.md @@ -0,0 +1,64 @@ +# Email Template + +## About + +The `email-template` component contains an `Email` class with two instance methods: `getBody` for retrieving the email body (html and text) and `sendEmail` for sending the email using the `send-email` component from PubSweet. + +1. `getBody({ body = {}, isReviewerInvitation = false })`: + * accepts two parameters: + * the `body` object, which for simple notifications should have only two properties: `paragraph` and `hasLink` + * the `isReviewerInvitation` boolean to indicate wether or not you need to send a Reviewer Invitation, which uses a more complex template + * returns the HTML and text parts of the email which can then be used to send it +1. `sendEmail({ text, html })`: + * accepts the text and HTML parts of an email and then uses the `send-email` component from PubSweet to actually send the email. + +The `Email` class also provides a `constructor` whose properties will be used when sending the email: + +1. `type`: a String that can be either `user` or `system` which can be used in the unsubscribe process +1. `toUser`: an Object with two properties: `email` and `name`. The `name` property will be used when addressing the recipient in the email content - for example: "Dear Dr. Rachel Smith". +1. `content`: an Object which contains data about the email: + 1. `subject` + 1. `signatureName` + 1. `ctaLink` - the URL which will be placed in the button + 1. `ctaText` - the text which appears on the button + 1. `unsubscribeLink` + +## Usage + +1. **Notifications** + These are the most basic emails, which contain at least a piece of text, called a `Paragraph` and may or may not contain an `Action Button`. The `paragraph` and `hasLink` are passed to the `getBody()` function as properties of the `body` parameter. + +  + + ```javascript + const emailTemplate = require('@pubsweet/component-email-template') + + const sendNotifications = ({ user, editor, collection, fragment }) => { + const email = new emailTemplate({ + type: 'user', + toUser: { + email: user.email, + name: `${user.firstName} ${user.lastName}`, + }, + content: { + subject: `${collection.customId}: Manuscript Update`, + signatureName: `${editor.name}`, + ctaLink: `http://localhost:3000/projects/${collection.id}/versions/${ + fragment.id + }/details`, + ctaText: 'MANUSCRIPT DETAILS', + unsubscribeLink: `http://localhost:3000/unsubscribe/${user.id}`, + }, + }) + + const paragraph = `We are please to inform you that the manuscript has passed the technical check process and is now submitted. Please click the link below to access the manuscript.` + + const { html, text } = email.getBody({ paragraph, hasLink: true }) + email.sendEmail({ html, text }) + } + ``` + +1. **Reviewer Invitation** + This email template is specific to Hindawi and it requires some data that might not be available in other PubSweet apps. + +  diff --git a/packages/component-email-templating/config/default.js b/packages/component-email-templating/config/default.js new file mode 100644 index 000000000..0d5f73e62 --- /dev/null +++ b/packages/component-email-templating/config/default.js @@ -0,0 +1,18 @@ +module.exports = { + // sender: { + // state: 'CA', + // zipCode: '94103', + // city: 'San Francisco', + // name: 'Coko Foundation', + // address: '2973 16th St., Suite 300', + // fromEmail: , + // }, + journal: { + name: 'Coko Foundation', + staffEmail: 'Coko <team@coko.foundation>', + logo: + 'https://marketing-image-production.s3.amazonaws.com/uploads/5870a9c1bd8e6213ef1e02718d2db115be163584a9fc401accb63866479b952fe83d646a32a55996a3701a0b40943c47900cb1c2d255ae34e7925555b2cdc24a.png', + ctaColor: '#EE2B77', + logoLink: 'https://coko.foundation/', + }, +} diff --git a/packages/component-email-templating/config/test.js b/packages/component-email-templating/config/test.js new file mode 100644 index 000000000..f41006674 --- /dev/null +++ b/packages/component-email-templating/config/test.js @@ -0,0 +1,3 @@ +const defaultConfig = require('./default') + +module.exports = defaultConfig diff --git a/packages/component-email-templating/index.js b/packages/component-email-templating/index.js new file mode 100644 index 000000000..3ad0f7ec7 --- /dev/null +++ b/packages/component-email-templating/index.js @@ -0,0 +1 @@ +module.exports = require('./src/EmailTemplate') diff --git a/packages/component-email-templating/package.json b/packages/component-email-templating/package.json new file mode 100644 index 000000000..4fee49627 --- /dev/null +++ b/packages/component-email-templating/package.json @@ -0,0 +1,33 @@ +{ + "name": "@pubsweet/component-email-templating", + "version": "0.0.1", + "description": "build email template and send email for pubsweet", + "license": "MIT", + "author": "Collaborative Knowledge Foundation", + "files": [ + "src" + ], + "main": "index.js", + "dependencies": { + "@pubsweet/component-send-email": "0.2.4", + "handlebars": "^4.0.2" + }, + "peerDependencies": { + "@pubsweet/logger": "^0.0.1" + }, + "repository": { + "type": "git", + "url": "https://gitlab.coko.foundation/pubsweet/pubsweet", + "path": "SendEmail" + }, + "jest": { + "verbose": true, + "testRegex": "/tests/.*.test.js$" + }, + "scripts": { + "test": "jest" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/component-email-templating/src/EmailTemplate.js b/packages/component-email-templating/src/EmailTemplate.js new file mode 100644 index 000000000..51d944c0e --- /dev/null +++ b/packages/component-email-templating/src/EmailTemplate.js @@ -0,0 +1,96 @@ +const config = require('config') +const helpers = require('./helpers') +const SendEmail = require('@pubsweet/component-send-email') + +const configData = {} +class Email { + constructor({ + type = 'system', + fromEmail = config.get('journal.fromEmail'), + toUser = { + id: '', + email: '', + name: '', + }, + content = { + subject: '', + ctaLink: '', + ctaText: '', + ctaColor: '', + signatureName: '', + unsubscribeLink: '', + signatureJournal: '', + }, + }) { + this.type = type + this.toUser = toUser + this.content = content + this.fromEmail = fromEmail + } + + set _toUser(newToUser) { + this.toUser = newToUser + } + + set _subject(newSubject) { + this.subject = newSubject + } + + set _content(newContent) { + this.content = newContent + } + + getInvitationBody({ emailBodyProps }) { + return { + html: helpers.getInvitationBody({ + replacements: { + ...configData, + ...this.content, + ...emailBodyProps, + toUserName: this.toUser.name, + }, + }), + text: `${emailBodyProps.resend} ${emailBodyProps.upperContent} ${ + emailBodyProps.manuscriptText + } ${emailBodyProps.lowerContent} ${this.content.signatureName}`, + } + } + + getNotificationBody({ emailBodyProps }) { + return { + html: helpers.getCompiledNotificationBody({ + replacements: { + ...configData, + ...this.content, + ...emailBodyProps, + toUserName: this.toUser.name, + }, + }), + text: `${emailBodyProps.paragraph} ${this.content.ctaLink} ${ + this.content.ctaText + } ${this.content.signatureName}`, + } + } + + async sendEmail({ text, html }) { + const { fromEmail: from } = this + const { email: to } = this.toUser + const { subject } = this.content + + const mailData = { + to, + text, + html, + from, + subject, + } + + try { + await SendEmail.send(mailData) + } catch (e) { + throw new Error(e) + } + } +} + +module.exports = Email diff --git a/packages/component-email-templating/src/helpers.js b/packages/component-email-templating/src/helpers.js new file mode 100644 index 000000000..8df0e7950 --- /dev/null +++ b/packages/component-email-templating/src/helpers.js @@ -0,0 +1,53 @@ +const fs = require('fs') +const handlebars = require('handlebars') + +const getCompiledNotificationBody = ({ replacements }) => { + handlePartial('header', replacements) + if (replacements.hasIntro) handlePartial('intro', replacements) + handlePartial('footer', replacements) + if (replacements.hasSignature) handlePartial('signature', replacements) + if (replacements.hasLink) handlePartial('button', replacements) + handlePartial('body', replacements) + + return compileBody({ fileName: 'notification', context: replacements }) +} + +const getCompiledInvitationBody = ({ replacements }) => { + handlePartial('invHeader', replacements) + handlePartial('footer', replacements) + handlePartial('invUpperContent', replacements) + handlePartial('invButtons', replacements) + handlePartial('invManuscriptData', replacements) + handlePartial('signature', replacements) + handlePartial('invLowerContent', replacements) + + return compileBody({ fileName: 'invitation', context: replacements }) +} + +const readFile = path => + fs.readFileSync(path, { encoding: 'utf-8' }, (err, file) => { + if (err) { + throw err + } else { + return file + } + }) + +const handlePartial = (partialName, context = {}) => { + let partial = readFile(`${__dirname}/templates/partials/${partialName}.hbs`) + const template = handlebars.compile(partial) + partial = template(context) + handlebars.registerPartial(partialName, partial) +} + +const compileBody = ({ fileName, context }) => { + const htmlFile = readFile(`${__dirname}/templates/${fileName}.html`) + const htmlTemplate = handlebars.compile(htmlFile) + const htmlBody = htmlTemplate(context) + return htmlBody +} + +module.exports = { + getCompiledNotificationBody, + getCompiledInvitationBody, +} diff --git a/packages/component-email-templating/src/templates/invitation.html b/packages/component-email-templating/src/templates/invitation.html new file mode 100644 index 000000000..f9d5ed18c --- /dev/null +++ b/packages/component-email-templating/src/templates/invitation.html @@ -0,0 +1,5 @@ +{{> invHeader }} +{{> invUpperContent }} +{{> invButtons }} +{{> invLowerContent }} +{{> footer }} \ No newline at end of file diff --git a/packages/component-email-templating/src/templates/notification.html b/packages/component-email-templating/src/templates/notification.html new file mode 100644 index 000000000..02cbf2a6e --- /dev/null +++ b/packages/component-email-templating/src/templates/notification.html @@ -0,0 +1,3 @@ +{{> header }} +{{> body }} +{{> footer}} \ No newline at end of file diff --git a/packages/component-email-templating/src/templates/partials/body.hbs b/packages/component-email-templating/src/templates/partials/body.hbs new file mode 100644 index 000000000..59e123063 --- /dev/null +++ b/packages/component-email-templating/src/templates/partials/body.hbs @@ -0,0 +1,25 @@ +<table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;"> + <tr> + <td style="padding:30px 23px 0px 23px;background-color:#ffffff;" height="100%" valign="top" bgcolor="#ffffff"> + <div> + {{#if hasIntro}} + {{> intro}} + {{/if}} + <p> </p> + <p> + {{{paragraph}}} + </p> + <p> </p> + <p> + {{#if hasLink }} + {{> button }} + {{/if}} + </p> + {{#if hasSignature }} + {{> signature}} + {{/if}} + <p> </p> + </div> + </td> + </tr> +</table> \ No newline at end of file diff --git a/packages/component-email-templating/src/templates/partials/button.hbs b/packages/component-email-templating/src/templates/partials/button.hbs new file mode 100644 index 000000000..95f80f289 --- /dev/null +++ b/packages/component-email-templating/src/templates/partials/button.hbs @@ -0,0 +1,19 @@ +<table border="0" cellPadding="0" cellSpacing="0" class="module" data-role="module-button" data-type="button" role="module" + style="table-layout:fixed" width="100%"> + <tbody> + <tr> + <td align="center" bgcolor="#FFFFFF" class="outer-td" style="padding:0px 0px 30px 0px;background-color:#FFFFFF"> + <table border="0" cellPadding="0" cellSpacing="0" class="button-css__deep-table___2OZyb wrapper-mobile" style="text-align:center"> + <tbody> + <tr> + <td align="center" bgcolor="{{ctaColor}}" class="inner-td" style="border-radius:6px;font-size:16px;text-align:center;background-color:inherit"> + <a href="{{ ctaLink }}" style="background-color:{{ctaColor}};border:1px solid #333333;border-color:{{ctaColor}};border-radius:0px;border-width:1px;color:#ffffff;display:inline-block;font-family:arial,helvetica,sans-serif;font-size:16px;font-weight:normal;letter-spacing:0px;line-height:16px;padding:12px 18px 12px 18px;text-align:center;text-decoration:none" + target="_blank">{{ ctaText }}</a> + </td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> +</table> \ No newline at end of file diff --git a/packages/component-email-templating/src/templates/partials/footer.hbs b/packages/component-email-templating/src/templates/partials/footer.hbs new file mode 100644 index 000000000..ef5a6c5d0 --- /dev/null +++ b/packages/component-email-templating/src/templates/partials/footer.hbs @@ -0,0 +1,36 @@ +<div data-role="module-unsubscribe" class="module unsubscribe-css__unsubscribe___2CDlR" role="module" data-type="unsubscribe" + style="color:#444444;font-size:12px;line-height:20px;padding:16px 16px 16px 16px;text-align:center"> + <div class="Unsubscribe--addressLine"> + <p class="Unsubscribe--senderName" style="font-family:Arial, Helvetica, sans-serif;font-size:12px;line-height:20px">{{ senderName }}</p> + <p style="font-family:Arial, Helvetica, sans-serif;font-size:12px;line-height:20px"> + <span class="Unsubscribe--senderAddress">{{ senderAddress }}</span>, + <span class="Unsubscribe--senderCity">{{ senderCity }}</span>, + <span class="Unsubscribe--senderState">{{ senderState }}</span> + <span class="Unsubscribe--senderZip">{{ senderZip }}</span> + </p> + </div> + <p style="font-family:Arial, Helvetica, sans-serif;font-size:12px;line-height:20px"> + <a class="Unsubscribe--unsubscribeLink" href="{{ unsubscribeLink }}">Unsubscribe</a> + </p> +</div> +</td> +</tr> +</table> +<!--[if mso]> + </td></tr></table> + </center> + <![endif]--> +</td> +</tr> +</table> +</td> +</tr> +</table> +</td> +</tr> +</table> +</div> +</center> +</body> + +</html> \ No newline at end of file diff --git a/packages/component-email-templating/src/templates/partials/header.hbs b/packages/component-email-templating/src/templates/partials/header.hbs new file mode 100644 index 000000000..fbc2385fb --- /dev/null +++ b/packages/component-email-templating/src/templates/partials/header.hbs @@ -0,0 +1,163 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1" /> + <!--[if !mso]><!--> + <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> + <!--<![endif]--> + <!--[if (gte mso 9)|(IE)]> + <xml> + <o:OfficeDocumentSettings> + <o:AllowPNG/> + <o:PixelsPerInch>96</o:PixelsPerInch> + </o:OfficeDocumentSettings> + </xml> + <![endif]--> + <!--[if (gte mso 9)|(IE)]> + <style type="text/css"> + body {width: 600px;margin: 0 auto;} + table {border-collapse: collapse;} + table, td {mso-table-lspace: 0pt;mso-table-rspace: 0pt;} + img {-ms-interpolation-mode: bicubic;} + </style> + <![endif]--> + + <style type="text/css"> + body, + p, + div { + font-family: helvetica, arial, sans-serif; + font-size: 14px; + } + + body { + color: #626262; + } + + body a { + color: #0D78F2; + text-decoration: none; + } + + p { + margin: 0; + padding: 0; + } + + table.wrapper { + width: 100% !important; + table-layout: fixed; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -moz-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + } + + img.max-width { + max-width: 100% !important; + } + + .column.of-2 { + width: 50%; + } + + .column.of-3 { + width: 33.333%; + } + + .column.of-4 { + width: 25%; + } + + @media screen and (max-width:480px) { + .preheader .rightColumnContent, + .footer .rightColumnContent { + text-align: left !important; + } + .preheader .rightColumnContent div, + .preheader .rightColumnContent span, + .footer .rightColumnContent div, + .footer .rightColumnContent span { + text-align: left !important; + } + .preheader .rightColumnContent, + .preheader .leftColumnContent { + font-size: 80% !important; + padding: 5px 0; + } + table.wrapper-mobile { + width: 100% !important; + table-layout: fixed; + } + img.max-width { + height: auto !important; + max-width: 480px !important; + } + a.bulletproof-button { + display: block !important; + width: auto !important; + font-size: 80%; + padding-left: 0 !important; + padding-right: 0 !important; + } + .columns { + width: 100% !important; + } + .column { + display: block !important; + width: 100% !important; + padding-left: 0 !important; + padding-right: 0 !important; + margin-left: 0 !important; + margin-right: 0 !important; + } + } + </style> + <!--user entered Head Start--> + + <!--End Head user entered--> +</head> + +<body> + <center class="wrapper" data-link-color="#0D78F2" data-body-style="font-size: 14px; font-family: helvetica,arial,sans-serif; color: #626262; background-color: #F4F4F4;"> + <div class="webkit"> + <table cellpadding="0" cellspacing="0" border="0" width="100%" class="wrapper" bgcolor="#F4F4F4"> + <tr> + <td valign="top" bgcolor="#F4F4F4" width="100%"> + <table width="100%" role="content-container" class="outer" align="center" cellpadding="0" cellspacing="0" border="0"> + <tr> + <td width="100%"> + <table width="100%" cellpadding="0" cellspacing="0" border="0"> + <tr> + <td> + <!--[if mso]> + <center> + <table><tr><td width="600"> + <![endif]--> + <table width="100%" cellpadding="0" cellspacing="0" border="0" style="width: 100%; max-width:600px;" align="center"> + <tr> + <td role="modules-container" style="padding: 0px 0px 0px 0px; color: #626262; text-align: left;" bgcolor="#F4F4F4" width="100%" + align="left"> + + <table class="module preheader preheader-hide" role="module" data-type="preheader" border="0" cellpadding="0" cellspacing="0" + width="100%" style="display: none !important; mso-hide: all; visibility: hidden; opacity: 0; color: transparent; height: 0; width: 0;"> + <tr> + <td role="module-content"> + <p>you have a new notification</p> + </td> + </tr> + </table> + + <table class="wrapper" role="module" data-type="image" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;"> + <tr> + <td style="font-size:6px;line-height:10px;padding:20px 0px 20px 0px;" valign="top" align="center"> + <a href="{{ logoLink }}"> + <img class="max-width" border="0" style="display:block;color:#000000;text-decoration:none;font-family:Helvetica, arial, sans-serif;font-size:16px;max-width:10% !important;width:10%;height:auto !important;" + src="{{ logo }}" + alt="" width="60"> + </a> + </td> + </tr> + </table> \ No newline at end of file diff --git a/packages/component-email-templating/src/templates/partials/intro.hbs b/packages/component-email-templating/src/templates/partials/intro.hbs new file mode 100644 index 000000000..0ce780b6d --- /dev/null +++ b/packages/component-email-templating/src/templates/partials/intro.hbs @@ -0,0 +1 @@ +<p data-pm-slice="1 1 []">Dear Dr. {{toUserName}},</p> \ No newline at end of file diff --git a/packages/component-email-templating/src/templates/partials/invButtons.hbs b/packages/component-email-templating/src/templates/partials/invButtons.hbs new file mode 100644 index 000000000..5c5041dec --- /dev/null +++ b/packages/component-email-templating/src/templates/partials/invButtons.hbs @@ -0,0 +1,88 @@ +<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" role="module" data-type="columns" data-version="2" + style="padding:20px 0px 20px 0px;background-color:#ffffff;box-sizing:border-box;" bgcolor="#ffffff"> + <tr role='module-content'> + <td height="100%" valign="top"> + <!--[if (gte mso 9)|(IE)]> + <center> + <table cellpadding="0" cellspacing="0" border="0" width="100%" style="border-spacing:0;border-collapse:collapse;table-layout: fixed;" > + <tr> + <![endif]--> + + <!--[if (gte mso 9)|(IE)]> + <td width="300.000px" valign="top" style="padding: 0px 0px 0px 0px;border-collapse: collapse;" > + <![endif]--> + + <table width="300.000" style="width:300.000px;border-spacing:0;border-collapse:collapse;margin:0px 0px 0px 0px;" cellpadding="0" + cellspacing="0" align="left" border="0" bgcolor="#ffffff" class="column column-0 of-2 + empty"> + <tr> + <td style="padding:0px;margin:0px;border-spacing:0;"> + <table border="0" cellPadding="0" cellSpacing="0" class="module" data-role="module-button" data-type="button" role="module" + style="table-layout:fixed" width="100%"> + <tbody> + <tr> + <td align="center" class="outer-td" style="padding:0px 0px 0px 0px"> + <table border="0" cellPadding="0" cellSpacing="0" class="button-css__deep-table___2OZyb wrapper-mobile" style="text-align:center"> + <tbody> + <tr> + <td align="center" bgcolor="#0d78f2" class="inner-td" style="border-radius:6px;font-size:16px;text-align:center;background-color:inherit"> + <a style="background-color:#0d78f2;border:1px solid #333333;border-color:#0d78f2;border-radius:0px;border-width:1px;color:#ffffff;display:inline-block;font-family:arial,helvetica,sans-serif;font-size:16px;font-weight:normal;letter-spacing:0px;line-height:16px;padding:12px 18px 12px 18px;text-align:center;text-decoration:none" + href="{{ agreeLink }}" target="_blank">AGREE</a> + </td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + </td> + </tr> + </table> + + <!--[if (gte mso 9)|(IE)]> + </td> + <![endif]--> + <!--[if (gte mso 9)|(IE)]> + <td width="300.000px" valign="top" style="padding: 0px 0px 0px 0px;border-collapse: collapse;" > + <![endif]--> + + <table width="300.000" style="width:300.000px;border-spacing:0;border-collapse:collapse;margin:0px 0px 0px 0px;" cellpadding="0" + cellspacing="0" align="left" border="0" bgcolor="#ffffff" class="column column-1 of-2 + empty"> + <tr> + <td style="padding:0px;margin:0px;border-spacing:0;"> + <table border="0" cellPadding="0" cellSpacing="0" class="module" data-role="module-button" data-type="button" role="module" + style="table-layout:fixed" width="100%"> + <tbody> + <tr> + <td align="center" class="outer-td" style="padding:0px 0px 0px 0px"> + <table border="0" cellPadding="0" cellSpacing="0" class="button-css__deep-table___2OZyb wrapper-mobile" style="text-align:center"> + <tbody> + <tr> + <td align="center" bgcolor="#e4dfdf" class="inner-td" style="border-radius:6px;font-size:16px;text-align:center;background-color:inherit"> + <a style="background-color:#e4dfdf;border:1px solid #333333;border-color:#E4DFDF;border-radius:0px;border-width:1px;color:#302e2e;display:inline-block;font-family:arial,helvetica,sans-serif;font-size:16px;font-weight:normal;letter-spacing:0px;line-height:16px;padding:12px 18px 12px 18px;text-align:center;text-decoration:none" + href="{{ declineLink }}" target="_blank">DECLINE</a> + </td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + </td> + </tr> + </table> + + <!--[if (gte mso 9)|(IE)]> + </td> + <![endif]--> + <!--[if (gte mso 9)|(IE)]> + <tr> + </table> + </center> + <![endif]--> + </td> + </tr> +</table> \ No newline at end of file diff --git a/packages/component-email-templating/src/templates/partials/invHeader.hbs b/packages/component-email-templating/src/templates/partials/invHeader.hbs new file mode 100644 index 000000000..d90452b55 --- /dev/null +++ b/packages/component-email-templating/src/templates/partials/invHeader.hbs @@ -0,0 +1,161 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1" /> + <!--[if !mso]><!--> + <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> + <!--<![endif]--> + <!--[if (gte mso 9)|(IE)]> + <xml> + <o:OfficeDocumentSettings> + <o:AllowPNG/> + <o:PixelsPerInch>96</o:PixelsPerInch> + </o:OfficeDocumentSettings> + </xml> + <![endif]--> + <!--[if (gte mso 9)|(IE)]> + <style type="text/css"> + body {width: 600px;margin: 0 auto;} + table {border-collapse: collapse;} + table, td {mso-table-lspace: 0pt;mso-table-rspace: 0pt;} + img {-ms-interpolation-mode: bicubic;} + </style> + <![endif]--> + + <style type="text/css"> + body, + p, + div { + font-family: helvetica, arial, sans-serif; + font-size: 14px; + } + + body { + color: #626262; + } + + body a { + color: #0D78F2; + text-decoration: none; + } + + p { + margin: 0; + padding: 0; + } + + table.wrapper { + width: 100% !important; + table-layout: fixed; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -moz-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + } + + img.max-width { + max-width: 100% !important; + } + + .column.of-2 { + width: 50%; + } + + .column.of-3 { + width: 33.333%; + } + + .column.of-4 { + width: 25%; + } + + @media screen and (max-width:480px) { + .preheader .rightColumnContent, + .footer .rightColumnContent { + text-align: left !important; + } + .preheader .rightColumnContent div, + .preheader .rightColumnContent span, + .footer .rightColumnContent div, + .footer .rightColumnContent span { + text-align: left !important; + } + .preheader .rightColumnContent, + .preheader .leftColumnContent { + font-size: 80% !important; + padding: 5px 0; + } + table.wrapper-mobile { + width: 100% !important; + table-layout: fixed; + } + img.max-width { + height: auto !important; + max-width: 480px !important; + } + a.bulletproof-button { + display: block !important; + width: auto !important; + font-size: 80%; + padding-left: 0 !important; + padding-right: 0 !important; + } + .columns { + width: 100% !important; + } + .column { + display: block !important; + width: 100% !important; + padding-left: 0 !important; + padding-right: 0 !important; + margin-left: 0 !important; + margin-right: 0 !important; + } + } + </style> + <!--user entered Head Start--> + + <!--End Head user entered--> +</head> + +<body> + <center class="wrapper" data-link-color="#0D78F2" data-body-style="font-size: 14px; font-family: helvetica,arial,sans-serif; color: #626262; background-color: #F4F4F4;"> + <div class="webkit"> + <table cellpadding="0" cellspacing="0" border="0" width="100%" class="wrapper" bgcolor="#F4F4F4"> + <tr> + <td valign="top" bgcolor="#F4F4F4" width="100%"> + <table width="100%" role="content-container" class="outer" align="center" cellpadding="0" cellspacing="0" border="0"> + <tr> + <td width="100%"> + <table width="100%" cellpadding="0" cellspacing="0" border="0"> + <tr> + <td> + <!--[if mso]> + <center> + <table><tr><td width="600"> + <![endif]--> + <table width="100%" cellpadding="0" cellspacing="0" border="0" style="width: 100%; max-width:600px;" align="center"> + <tr> + <td role="modules-container" style="padding: 0px 0px 0px 0px; color: #626262; text-align: left;" bgcolor="#F4F4F4" width="100%" + align="left"> + + <table class="module preheader preheader-hide" role="module" data-type="preheader" border="0" cellpadding="0" cellspacing="0" + width="100%" style="display: none !important; mso-hide: all; visibility: hidden; opacity: 0; color: transparent; height: 0; width: 0;"> + <tr> + <td role="module-content"> + <p>new invitation</p> + </td> + </tr> + </table> + + <table class="wrapper" role="module" data-type="image" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;"> + <tr> + <td style="font-size:6px;line-height:10px;padding:20px 0px 20px 0px;" valign="top" align="center"> + <img class="max-width" border="0" style="display:block;color:#000000;text-decoration:none;font-family:Helvetica, arial, sans-serif;font-size:16px;max-width:10% !important;width:10%;height:auto !important;" + src="{{ logo }}" + alt="" width="60"> + </td> + </tr> + </table> \ No newline at end of file diff --git a/packages/component-email-templating/src/templates/partials/invLowerContent.hbs b/packages/component-email-templating/src/templates/partials/invLowerContent.hbs new file mode 100644 index 000000000..2e391bc6b --- /dev/null +++ b/packages/component-email-templating/src/templates/partials/invLowerContent.hbs @@ -0,0 +1,20 @@ +<table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;"> + <tr> + <td style="padding:30px 23px 0px 23px;background-color:#ffffff;" height="100%" valign="top" bgcolor="#ffffff"> + <p data-pm-slice="1 1 []"> + <a href="{{ detailsLink }}">See more information</a> + </p> + + <p data-pm-slice="1 1 []"> </p> + + {{#unless resend }} + {{> invManuscriptData }} + {{/unless}} + <p data-pm-slice="1 1 []">{{{ lowerContent }}}</p> + + <p> </p> + {{> signature }} + <div style="text-align: center;"> </div> + </td> + </tr> +</table> \ No newline at end of file diff --git a/packages/component-email-templating/src/templates/partials/invManuscriptData.hbs b/packages/component-email-templating/src/templates/partials/invManuscriptData.hbs new file mode 100644 index 000000000..36d60dbe2 --- /dev/null +++ b/packages/component-email-templating/src/templates/partials/invManuscriptData.hbs @@ -0,0 +1,15 @@ +<p data-pm-slice="1 1 []">{{ manuscriptText }}</p> +<p> </p> +<h2>{{ title }}</h2> +<p> </p> +<h4> + <span style="font-family:arial,helvetica,sans-serif;">{{ authorsList }}</span> +</h4> +<p> + <br /> + <em> + <span style="font-family:arial,helvetica,sans-serif;">{{{abstract}}}</span> + </em> +</p> + +<p> </p> \ No newline at end of file diff --git a/packages/component-email-templating/src/templates/partials/invUpperContent.hbs b/packages/component-email-templating/src/templates/partials/invUpperContent.hbs new file mode 100644 index 000000000..2b9dc858c --- /dev/null +++ b/packages/component-email-templating/src/templates/partials/invUpperContent.hbs @@ -0,0 +1,13 @@ +<table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;"> + <tr> + <td style="padding:30px 23px 20px 23px;background-color:#ffffff;" height="100%" valign="top" bgcolor="#ffffff"> + <div> + <p data-pm-slice="1 1 []">Dear Dr. {{ toUserName }},</p> + + <p> </p> + <p>{{{ upperContent }}}</p> + </div> + + </td> + </tr> +</table> \ No newline at end of file diff --git a/packages/component-email-templating/src/templates/partials/signature.hbs b/packages/component-email-templating/src/templates/partials/signature.hbs new file mode 100644 index 000000000..eec0831b8 --- /dev/null +++ b/packages/component-email-templating/src/templates/partials/signature.hbs @@ -0,0 +1,4 @@ +<p>Kind regards, + <br /> {{ signatureName }} + <br /> {{ signatureJournal }} +</p> \ No newline at end of file diff --git a/packages/component-email-templating/tests/helpers.test.js b/packages/component-email-templating/tests/helpers.test.js new file mode 100644 index 000000000..e655ec191 --- /dev/null +++ b/packages/component-email-templating/tests/helpers.test.js @@ -0,0 +1,60 @@ +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' +process.env.SUPPRESS_NO_CONFIG_WARNING = true + +const { cloneDeep } = require('lodash') +const helpers = require('../src/helpers') + +const emailProps = { + toUserName: 'Peter Griffin', + paragraph: 'This is a paragraph', + hasLink: true, + ctaText: 'CLICK HERE', + hasIntro: true, + hasSignature: true, +} + +describe('Email template helpers', () => { + let replacements = {} + beforeEach(() => { + replacements = cloneDeep(emailProps) + }) + it('should return the notification HTML with CTA', () => { + const notificationBody = helpers.getCompiledNotificationBody({ + replacements, + }) + + expect(notificationBody).toContain('Peter Griffin') + expect(notificationBody).toContain('This is a paragraph') + expect(notificationBody).toContain('CLICK HERE') + expect(notificationBody).toContain('Dear Dr.') + expect(notificationBody).toContain('Kind regards') + }) + + it('should return the notification HTML without CTA', () => { + replacements.hasLink = false + const notificationBody = helpers.getCompiledNotificationBody({ + replacements, + }) + expect(notificationBody).toContain('Peter Griffin') + expect(notificationBody).toContain('This is a paragraph') + expect(notificationBody).not.toContain('CLICK HERE') + }) + it('should return the notification HTML without intro', () => { + replacements.hasIntro = false + const notificationBody = helpers.getCompiledNotificationBody({ + replacements, + }) + expect(notificationBody).not.toContain('Peter Griffin') + expect(notificationBody).toContain('This is a paragraph') + expect(notificationBody).not.toContain('Dear Dr.') + }) + it('should return the notification HTML without signature', () => { + replacements.hasSignature = false + const notificationBody = helpers.getCompiledNotificationBody({ + replacements, + }) + expect(notificationBody).toContain('Peter Griffin') + expect(notificationBody).toContain('This is a paragraph') + expect(notificationBody).not.toContain('Kind regards') + }) +}) diff --git a/packages/component-email/src/routes/emails/helpers.js b/packages/component-email/src/routes/emails/helpers.js index f0473d53b..8f484b0a5 100644 --- a/packages/component-email/src/routes/emails/helpers.js +++ b/packages/component-email/src/routes/emails/helpers.js @@ -18,8 +18,8 @@ module.exports = { emailType = 'user-added-by-admin' } - const { html, text } = email.getBody({ - body: getEmailCopy({ + const { html, text } = email.getNotificationBody({ + emailBodyProps: getEmailCopy({ role, emailType, }), @@ -34,8 +34,8 @@ module.exports = { confirmationToken: user.accessTokens.confirmation, }) - const { html, text } = email.getBody({ - body: getEmailCopy({ + const { html, text } = email.getNotificationBody({ + emailBodyProps: getEmailCopy({ emailType: 'user-signup', }), }) diff --git a/packages/component-email/src/routes/emails/notifications.js b/packages/component-email/src/routes/emails/notifications.js index 0d4f67258..10dd129a5 100644 --- a/packages/component-email/src/routes/emails/notifications.js +++ b/packages/component-email/src/routes/emails/notifications.js @@ -3,8 +3,8 @@ const config = require('config') const unsubscribeSlug = config.get('unsubscribe.url') const resetPath = config.get('invite-reset-password.url') const staffEmail = config.get('journal.staffEmail') - -const { Email, services } = require('pubsweet-component-helper-service') +const Email = require('@pubsweet/component-email-templating') +const { services } = require('pubsweet-component-helper-service') const { sendNewUserEmail, sendSignupEmail } = require('./helpers') diff --git a/packages/xpub-faraday/app/config/journal/metadata.js b/packages/xpub-faraday/app/config/journal/metadata.js index d23e29ac8..38c8bb3b5 100644 --- a/packages/xpub-faraday/app/config/journal/metadata.js +++ b/packages/xpub-faraday/app/config/journal/metadata.js @@ -13,4 +13,10 @@ module.exports = { issn: '2474-7394', prefix: 'RESEARCH-BCA-', }, + emailData: { + logo: + 'https://marketing-image-production.s3.amazonaws.com/uploads/bb39b20cf15e52c1c0933676e25f2b2402737c6560b8098c204ad6932b84eb2058804376dbc4db138c7a21dcaed9325bde36185648afac5bc97e3d73d4e12718.png', + ctaColor: '#63a945', + logoLink: 'https://hindawi.com', + }, } diff --git a/packages/xpub-faraday/config/components.json b/packages/xpub-faraday/config/components.json index bbb47dcc4..01db85a3f 100644 --- a/packages/xpub-faraday/config/components.json +++ b/packages/xpub-faraday/config/components.json @@ -6,6 +6,7 @@ "pubsweet-components-faraday", "@pubsweet/component-aws-s3", "@pubsweet/component-send-email", + "@pubsweet/component-email-templating", "pubsweet-component-invite", "pubsweet-component-user-manager", "pubsweet-component-email", diff --git a/packages/xpub-faraday/config/default.js b/packages/xpub-faraday/config/default.js index b00ae42ca..5122682df 100644 --- a/packages/xpub-faraday/config/default.js +++ b/packages/xpub-faraday/config/default.js @@ -123,6 +123,9 @@ module.exports = { journal: { name: get(journalConfig, 'metadata.nameText'), staffEmail: get(journalConfig, 'metadata.email'), + logo: get(journalConfig, 'metadata.emailData.logo'), + ctaColor: get(journalConfig, 'metadata.emailData.ctaColor'), + logoLink: get(journalConfig, 'metadata.emailData.logoLink'), }, features: { mts: JSON.parse(get(process, 'env.FEATURE_MTS', true)), -- GitLab