Skip to content

Commit

Permalink
feat(emails): add email template with wrapper (#1588)
Browse files Browse the repository at this point in the history
* feat(emails): add header and footer

Co-authored-by: Priya Chatwani <[email protected]>
Co-authored-by: Hiemanshu Sharma <[email protected]>
  • Loading branch information
3 people authored Sep 11, 2023
1 parent 68fb0fa commit 94fc8fa
Show file tree
Hide file tree
Showing 7 changed files with 837 additions and 68 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@babel/runtime": "^7.9.2",
"@babel/runtime-corejs3": "^7.9.2",
"@escape.tech/graphql-armor": "^1.4.0",
"@faire/mjml-react": "^3.3.0",
"@google-cloud/storage": "^5.7.4",
"@graphql-tools/mock": "^8.4.0",
"@graphql-tools/schema": "^8.2.0",
Expand Down Expand Up @@ -142,6 +143,7 @@
"material-ui-datatables": "^0.18.2",
"md5": "^2.2.1",
"memoredis": "^2.0.0",
"mjml": "^4.14.1",
"mysql": "^2.17.1",
"nexmo": "^1.0.0-beta-7",
"node-cron": "^2.0.3",
Expand Down
40 changes: 30 additions & 10 deletions src/server/lib/templates/digest.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from "react";
import ReactDOMServer from "react-dom/server";
import assemblePalette from "src/styles/assemble-palette";

import type { CampaignRecord, OrganizationRecord } from "../../api/types";
import { NotificationTypes } from "../../api/types";
import TemplateWrapper from "./template-wrapper";

interface FormattedNotification {
notificationType: NotificationTypes;
Expand All @@ -21,6 +23,20 @@ interface NotificationContentProps {
notification: FormattedNotification;
}

const styles = {
font: {
fontFamily: "Helvetica"
},
button: {
backgroundColor: assemblePalette.primary.navy,
padding: `12px 20px`,
border: "none",
color: "white",
borderRadius: 4,
marginBottom: 10
}
};

const AssignmentCreatedRow: React.FC<NotificationContentProps> = ({
notification
}) => {
Expand Down Expand Up @@ -73,19 +89,23 @@ const Digest: React.FC<DigestProps> = ({
textingUrl,
settingsUrl
}) => {
const orgName = organization.name;
return (
<div>
<p>You have outstanding text assignments from {organization.name}</p>
<TemplateWrapper organizationName={orgName} settingsUrl={settingsUrl}>
<p>Hello!</p>
<p>You have outstanding text assignments from {orgName}: </p>
{notifications.map((notification) => renderDigestRow(notification))}
<p>
You can start sending texts right away here:{" "}
<a href={textingUrl}>{textingUrl}</a>
</p>
<button
type="button"
style={styles.button}
onClick={() => {
window.open(textingUrl);
}}
>
Send Now
</button>
<br />
<p>
To modify your notification settings, go <a href={settingsUrl}>here</a>
</p>
</div>
</TemplateWrapper>
);
};

Expand Down
35 changes: 35 additions & 0 deletions src/server/lib/templates/footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { MjmlColumn, MjmlSection, MjmlText } from "@faire/mjml-react";
import React from "react";

interface FooterProps {
orgName: string;
settingsUrl: string;
}

const Footer: React.FC<FooterProps> = ({ orgName, settingsUrl }) => {
return (
<MjmlSection fullWidth>
<MjmlColumn>
<MjmlText>
<p>© 2023 Rewired, LLC. All rights reserved.</p>
<p>
You are receiving this email because you signed up to text for{" "}
{orgName}
.
<br />
<a href={settingsUrl}>Update</a> your preferences or unsubscribe{" "}
<a href={settingsUrl}>here</a>
.
<br />
</p>
<p>
Rewired LLC <br />
41 Flatbush Ave Fl 1, PMB 731 <br />
Brooklyn, NY 11217 <br />
</p>
</MjmlText>
</MjmlColumn>
</MjmlSection>
);
};
export default Footer;
14 changes: 14 additions & 0 deletions src/server/lib/templates/header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { MjmlColumn, MjmlImage, MjmlSection } from "@faire/mjml-react";
import React from "react";

const Header: React.FC = () => {
return (
<MjmlSection fullWidth>
<MjmlColumn>
<MjmlImage src="https://i.imgur.com/xxxV8Jo.png" />
</MjmlColumn>
</MjmlSection>
);
};

export default Header;
95 changes: 59 additions & 36 deletions src/server/lib/templates/notification.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { renderToMjml } from "@faire/mjml-react/utils/renderToMjml";
import mjml2html from "mjml";
import React from "react";
import ReactDOMServer from "react-dom/server";

import assemblePalette from "../../../styles/assemble-palette";
import type { CampaignRecord, OrganizationRecord } from "../../api/types";
import { NotificationTypes } from "../../api/types";
import TemplateWrapper from "./template-wrapper";

interface NotificationProps {
campaign: CampaignRecord;
Expand All @@ -12,29 +15,46 @@ interface NotificationProps {
textingUrl: string;
}

const styles = {
font: {
fontFamily: "Helvetica"
},
button: {
width: 80,
backgroundColor: assemblePalette.primary.navy,
padding: `12px 20px`,
borderRadius: 4,
cursor: "pointer",
marginBottom: 10
},
buttonText: {
color: "white",
textDecoration: "none"
}
};

const AssignmentCreated: React.FC<NotificationProps> = ({
organization,
campaign,
assignmentCount,
textingUrl,
settingsUrl
}) => {
const orgName = organization.name;
return (
<div>
<p>You just got a new texting assignment from {organization.name}</p>
<p>
[{campaign.title}]: {assignmentCount} first messages to send
</p>
<br />
<TemplateWrapper organizationName={orgName} settingsUrl={settingsUrl}>
<p>Hello!</p>
<p>
You can start sending texts right away here:{" "}
<a href={textingUrl}>{textingUrl}</a>
You just got a new texting assignment from {orgName}: {campaign.title}.
</p>
<p>There are {assignmentCount} first message(s) to send.</p>
<div style={styles.button}>
<a style={styles.buttonText} href={textingUrl}>
Send Now
</a>
</div>
<br />
<p>
To modify your notification settings, go <a href={settingsUrl}>here</a>
</p>
</div>
</TemplateWrapper>
);
};

Expand All @@ -45,22 +65,26 @@ const AssignmentUpdated: React.FC<NotificationProps> = ({
textingUrl,
settingsUrl
}) => {
const orgName = organization.name;
return (
<div>
<p>Your texting assignment from {organization.name} has been updated.</p>
<TemplateWrapper organizationName={orgName} settingsUrl={settingsUrl}>
<p>Hello!</p>
<p>
[{campaign.title}]: {assignmentCount} first messages to send.{" "}
Your texting assignment from {orgName}: {campaign.title} has been
updated.
</p>
<br />
<p>
You can start sending texts right away here:{" "}
<a href={textingUrl}>{textingUrl}</a>
{assignmentCount === "1"
? `There is one message to send. `
: `There are ${assignmentCount} messages to send. `}
</p>
<div style={styles.button}>
<a style={styles.buttonText} href={textingUrl}>
Send Now
</a>
</div>
<br />
<p>
To modify your notification settings, go <a href={settingsUrl}>here</a>
</p>
</div>
</TemplateWrapper>
);
};

Expand All @@ -70,22 +94,21 @@ const AssignmentMessageReceived: React.FC<NotificationProps> = ({
textingUrl,
settingsUrl
}) => {
const orgName = organization.name;
return (
<div>
<p>
Someone responded to your message from ${organization.name} in $
{campaign.title}
</p>
<br />
<TemplateWrapper organizationName={orgName} settingsUrl={settingsUrl}>
<p>Hello!</p>
<p>
You can look at your pending texts here:{" "}
<a href={textingUrl}>{textingUrl}</a>
Someone responded to your message from {orgName} in {campaign.title}.
Check out your pending texts!
</p>
<div style={styles.button}>
<a style={styles.buttonText} href={textingUrl}>
Send Now
</a>
</div>
<br />
<p>
To modify your notification settings, go <a href={settingsUrl}>here</a>
</p>
</div>
</TemplateWrapper>
);
};

Expand Down Expand Up @@ -114,7 +137,7 @@ const getNotificationContent = (
throw new Error(`Unrecognized notification type ${notificationType}`);
}

const content = ReactDOMServer.renderToStaticMarkup(template);
const content = mjml2html(renderToMjml(template)).html;

return {
content,
Expand Down
53 changes: 53 additions & 0 deletions src/server/lib/templates/template-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
Mjml,
MjmlAll,
MjmlAttributes,
MjmlBody,
MjmlColumn,
MjmlHead,
MjmlSection,
MjmlText
} from "@faire/mjml-react";
import React from "react";

import Footer from "./footer";
import Header from "./header";

export interface TemplateWrapperProps {
children: React.ReactNode;
organizationName: string;
settingsUrl: string;
}

export const TemplateWrapper: React.FC<TemplateWrapperProps> = ({
children,
organizationName,
settingsUrl
}) => {
return (
<Mjml>
<MjmlHead>
<MjmlAttributes>
<MjmlAll fontFamily="Helvetica, Arial, sans-serif" />
<MjmlText
fontWeight="400"
fontSize="16px"
color="#000000"
lineHeight="24px"
/>
</MjmlAttributes>
</MjmlHead>
<MjmlBody>
<Header />
<MjmlSection fullWidth backgroundColor="#f3f3f3">
<MjmlColumn>
<MjmlText>{children}</MjmlText>
</MjmlColumn>
</MjmlSection>
<Footer orgName={organizationName} settingsUrl={settingsUrl} />
</MjmlBody>
</Mjml>
);
};

export default TemplateWrapper;
Loading

0 comments on commit 94fc8fa

Please sign in to comment.