💄 Updated transactional email templates (#1285)

This commit is contained in:
Luke Vella 2024-08-30 17:18:36 +01:00 committed by GitHub
parent 4078618afd
commit 2f00dac998
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 186 additions and 234 deletions

View file

@ -2,10 +2,12 @@ export type EmailContext = {
logoUrl: string;
baseUrl: string;
domain: string;
supportEmail: string;
};
export const defaultEmailContext = {
logoUrl: "https://rallly.co/logo.png",
logoUrl: "https://rallly-public.s3.amazonaws.com/images/rallly-logo-mark.png",
baseUrl: "https://rallly.co",
domain: "rallly.co",
supportEmail: "support@rallly.co",
};

View file

@ -4,97 +4,56 @@ import {
Head,
Html,
Img,
Link,
Preview,
Section,
} from "@react-email/components";
import { EmailContext } from "./email-context";
import { fontFamily, Section, Text } from "./styled-components";
import { darkTextColor, fontFamily, Link, Text } from "./styled-components";
export interface EmailLayoutProps {
preview: string;
recipientName?: string;
footNote?: React.ReactNode;
ctx: EmailContext;
}
const containerStyles = {
maxWidth: "600px",
maxWidth: "480px",
margin: "0 auto",
background: "white",
fontFamily,
padding: 16,
border: "1px solid #E2E8F0",
borderRadius: 5,
};
const sectionStyles = {
marginTop: "16px",
marginBottom: "16px",
};
const linkStyles = {
color: "#64748B",
marginRight: "8px",
padding: "32px 8px",
color: darkTextColor,
};
export const EmailLayout = ({
preview,
recipientName,
children,
footNote,
ctx,
}: React.PropsWithChildren<EmailLayoutProps>) => {
const { logoUrl, baseUrl } = ctx;
const { logoUrl } = ctx;
return (
<Html>
<Head />
<Preview>{preview}</Preview>
<Body
style={{
backgroundColor: "#F3F4F6",
paddingTop: 20,
paddingBottom: 20,
}}
>
<Body style={{ backgroundColor: "#FFFFFF" }}>
<Container style={containerStyles}>
<Img src={logoUrl} alt="Rallly" width={128} />
<Section style={sectionStyles}>
{recipientName ? <Text>Hi {recipientName},</Text> : null}
{children}
{footNote ? (
<Text
style={{
color: "#64748B",
fontFamily,
paddingTop: 16,
marginTop: 32,
borderTop: "1px solid #e2e8f0",
}}
>
{footNote}
</Text>
) : null}
</Section>
<Section style={{ ...sectionStyles, fontSize: 14, marginBottom: 0 }}>
<Link style={linkStyles} href={baseUrl}>
Home
</Link>
<span>&nbsp;&bull;&nbsp;</span>
<Link style={linkStyles} href="https://twitter.com/ralllyco">
Twitter
</Link>
<span>&nbsp;&bull;&nbsp;</span>
<Link style={linkStyles} href="https://github.com/lukevella/rallly">
Github
</Link>
<span>&nbsp;&bull;&nbsp;</span>
<Link
style={linkStyles}
href={`mailto:${process.env["SUPPORT_EMAIL"]}`}
>
Contact
</Link>
<Img
src={logoUrl}
width="32"
height="32"
style={{
marginBottom: 32,
}}
alt="Rallly Logo"
/>
{children}
<Section style={{ marginTop: 32, textAlign: "center" }}>
<Text light={true}>
Powered by{" "}
<Link href="https://rallly.co?utm_source=email&utm_medium=transactional">
rallly.co
</Link>
</Text>
</Section>
</Container>
</Body>

View file

@ -1,3 +1,5 @@
import { Section } from "@react-email/section";
import { EmailContext } from "./email-context";
import { EmailLayout } from "./email-layout";
import { Button, Link, Text } from "./styled-components";
@ -15,7 +17,6 @@ export interface NotificationEmailProps extends NotificationBaseProps {
}
export const NotificationEmail = ({
name,
pollUrl,
disableNotificationsUrl,
preview,
@ -24,23 +25,17 @@ export const NotificationEmail = ({
}: React.PropsWithChildren<NotificationEmailProps>) => {
const { domain } = ctx;
return (
<EmailLayout
ctx={ctx}
recipientName={name}
footNote={
<>
If you would like to stop receiving updates you can{" "}
<Link className="whitespace-nowrap" href={disableNotificationsUrl}>
turn notifications off
</Link>
.
</>
}
preview={preview}
>
<EmailLayout ctx={ctx} preview={preview}>
{children}
<Text>
<Section style={{ marginTop: 32, marginBottom: 32 }}>
<Button href={pollUrl}>View on {domain}</Button>
</Section>
<Text light={true}>
If you would like to stop receiving updates you can{" "}
<Link className="whitespace-nowrap" href={disableNotificationsUrl}>
turn notifications off
</Link>
.
</Text>
</EmailLayout>
);

View file

@ -11,6 +11,8 @@ import {
import { EmailContext } from "./email-context";
export const lightTextColor = "#4B5563";
export const darkTextColor = "#1F2937";
export const borderColor = "#E2E8F0";
export const Text = (
props: TextProps & { light?: boolean; small?: boolean },
@ -23,7 +25,7 @@ export const Text = (
margin: "16px 0",
fontFamily,
fontSize: small ? "14px" : "16px",
color: light ? "#64748B" : "#334155",
color: light ? lightTextColor : darkTextColor,
lineHeight: "1.5",
...props.style,
}}
@ -46,6 +48,11 @@ export const Button = (props: React.ComponentProps<typeof UnstyledButton>) => {
borderRadius: "4px",
padding: "12px 14px",
fontFamily,
boxSizing: "border-box",
display: "block",
width: "100%",
maxWidth: "100%",
textAlign: "center",
fontSize: "16px",
color: "white",
}}
@ -62,23 +69,24 @@ export const Link = (props: LinkProps) => {
);
};
const fontSize = {
h1: "20px",
h2: "18px",
h3: "16px",
h4: "16px",
h5: "14px",
h6: "12px",
};
export const Heading = (
props: React.ComponentProps<typeof UnstyledHeading>,
) => {
const { as = "h3" } = props;
const fontSize = {
h1: "32px",
h2: "24px",
h3: "20px",
h4: "16px",
h5: "14px",
h6: "12px",
};
const { as = "h1" } = props;
return (
<UnstyledHeading
{...props}
as={as}
className="font-sans font-bold text-gray-900"
style={{
fontSize: fontSize[as],
...props.style,
@ -146,3 +154,5 @@ export const trackingWide = {
export const fontFamily =
"'Inter UI', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif";
export const primaryColor = "#4F46E5";

View file

@ -2,7 +2,12 @@ import { Column, Row, Section } from "@react-email/components";
import { defaultEmailContext, EmailContext } from "./_components/email-context";
import { EmailLayout } from "./_components/email-layout";
import { borderColor, Button, Text } from "./_components/styled-components";
import {
borderColor,
Button,
Heading,
Text,
} from "./_components/styled-components";
export interface FinalizeHostEmailProps {
date: string;
@ -18,7 +23,6 @@ export interface FinalizeHostEmailProps {
}
export const FinalizeHostEmail = ({
name = "Guest",
title = "Untitled Poll",
pollUrl = "https://rallly.co",
day = "12",
@ -28,7 +32,8 @@ export const FinalizeHostEmail = ({
ctx = defaultEmailContext,
}: FinalizeHostEmailProps) => {
return (
<EmailLayout ctx={ctx} recipientName={name} preview="Final date booked!">
<EmailLayout ctx={ctx} preview="Final date booked!">
<Heading>Final date booked!</Heading>
<Text>
<strong>{title}</strong> has been booked for:
</Text>
@ -73,9 +78,9 @@ export const FinalizeHostEmail = ({
<Text>
We&apos;ve notified participants and sent them calendar invites.
</Text>
<Text>
<Section style={{ marginTop: 32 }}>
<Button href={pollUrl}>View Event</Button>
</Text>
</Section>
</EmailLayout>
);
};

View file

@ -2,7 +2,12 @@ import { Column, Row, Section } from "@react-email/components";
import { defaultEmailContext, EmailContext } from "./_components/email-context";
import { EmailLayout } from "./_components/email-layout";
import { borderColor, Button, Text } from "./_components/styled-components";
import {
borderColor,
Button,
Heading,
Text,
} from "./_components/styled-components";
export interface FinalizeParticipantEmailProps {
date: string;
@ -19,7 +24,6 @@ export interface FinalizeParticipantEmailProps {
}
export const FinalizeParticipantEmail = ({
name = "Guest",
title = "Untitled Poll",
hostName = "Host",
pollUrl = "https://rallly.co",
@ -30,12 +34,13 @@ export const FinalizeParticipantEmail = ({
ctx = defaultEmailContext,
}: FinalizeParticipantEmailProps) => {
return (
<EmailLayout ctx={ctx} recipientName={name} preview="Final date booked!">
<EmailLayout ctx={ctx} preview="Final date booked!">
<Heading>Final date booked!</Heading>
<Text>
<strong>{hostName}</strong> has booked <strong>{title}</strong> for the
following date:
</Text>
<Section>
<Section data-testid="date-section">
<Row>
<Column style={{ width: 48 }}>
<Section
@ -74,9 +79,9 @@ export const FinalizeParticipantEmail = ({
</Row>
</Section>
<Text>Please find attached a calendar invite for this event.</Text>
<Text>
<Section style={{ marginTop: 32 }}>
<Button href={pollUrl}>View Event</Button>
</Text>
</Section>
</EmailLayout>
);
};

View file

@ -1,3 +1,5 @@
import { Section } from "@react-email/components";
import { defaultEmailContext, EmailContext } from "./_components/email-context";
import { EmailLayout } from "./_components/email-layout";
import {
@ -17,43 +19,40 @@ interface LoginEmailProps {
}
export const LoginEmail = ({
name = "Guest",
code = "123456",
magicLink = "https://rallly.co",
ctx = defaultEmailContext,
}: LoginEmailProps) => {
return (
<EmailLayout
ctx={ctx}
footNote={
<>
You&apos;re receiving this email because a request was made to login
to <Domain ctx={ctx} />. If this wasn&apos;t you, let us know by
replying to this email.
</>
}
recipientName={name}
preview="Use this link to log in on this device."
>
<Text>
To log in to your account, please choose one of the following options:
</Text>
<Card>
<Heading>Option 1: Magic Link</Heading>
<Text>Click this magic link to log in on this device.</Text>
<EmailLayout ctx={ctx} preview="Use this link to log in on this device.">
<Heading>Login</Heading>
<Text>Enter this one-time 6-digit verification code:</Text>
<Card style={{ textAlign: "center" }}>
<Text
style={{
...trackingWide,
textAlign: "center",
fontSize: "32px",
fontWeight: "bold",
}}
id="code"
>
{code}
</Text>
<Text style={{ textAlign: "center" }} light={true}>
This code is valid for 15 minutes
</Text>
</Card>
<Section style={{ marginBottom: 32 }}>
<Button href={magicLink} id="magicLink">
Log in to {ctx.domain}
</Button>
<Text light={true}>This link will expire in 15 minutes.</Text>
</Card>
<Card>
<Heading>Option 2: Verification Code</Heading>
<Text>Enter this one-time 6-digit verification code.</Text>
<Heading as="h1" style={trackingWide} id="code">
{code}
</Heading>
<Text light={true}>This code will expire in 15 minutes.</Text>
</Card>
</Section>
<Text light>
You&apos;re receiving this email because a request was made to login to{" "}
<Domain ctx={ctx} />. If this wasn&apos;t you contact{" "}
<a href={`mailto:${ctx.supportEmail}`}>{ctx.supportEmail}</a>.
</Text>
</EmailLayout>
);
};

View file

@ -2,7 +2,7 @@ import { defaultEmailContext } from "./_components/email-context";
import NotificationEmail, {
NotificationBaseProps,
} from "./_components/notification-email";
import { Text } from "./_components/styled-components";
import { Heading, Text } from "./_components/styled-components";
export interface NewCommentEmailProps extends NotificationBaseProps {
authorName: string;
@ -25,6 +25,7 @@ export const NewCommentEmail = ({
disableNotificationsUrl={disableNotificationsUrl}
preview="Go to your poll to see what they said."
>
<Heading>New Comment</Heading>
<Text>
<strong>{authorName}</strong> has commented on <strong>{title}</strong>.
</Text>

View file

@ -1,6 +1,12 @@
import { defaultEmailContext, EmailContext } from "./_components/email-context";
import { EmailLayout } from "./_components/email-layout";
import { Button, Domain, Section, Text } from "./_components/styled-components";
import {
Button,
Domain,
Heading,
Section,
Text,
} from "./_components/styled-components";
interface NewParticipantConfirmationEmailProps {
name: string;
@ -10,24 +16,13 @@ interface NewParticipantConfirmationEmailProps {
}
export const NewParticipantConfirmationEmail = ({
title = "Untitled Poll",
name = "John",
editSubmissionUrl = "https://rallly.co",
ctx = defaultEmailContext,
}: NewParticipantConfirmationEmailProps) => {
const { domain } = ctx;
return (
<EmailLayout
ctx={ctx}
footNote={
<>
You are receiving this email because a response was submitted on{" "}
<Domain ctx={ctx} />. If this wasn&apos;t you, please ignore this
email.
</>
}
recipientName={name}
preview="To edit your response use the link below"
>
<EmailLayout ctx={ctx} preview="To edit your response use the link below">
<Heading>Poll Response Confirmation</Heading>
<Text>
Your response to <strong>{title}</strong> has been submitted.
</Text>
@ -35,11 +30,15 @@ export const NewParticipantConfirmationEmail = ({
While the poll is still open you can change your response using the link
below.
</Text>
<Section>
<Section style={{ marginTop: 32 }}>
<Button id="editSubmissionUrl" href={editSubmissionUrl}>
Review response on {domain}
</Button>
</Section>
<Text light>
You are receiving this email because a response was submitted on{" "}
<Domain ctx={ctx} />. If this wasn&apos;t you, please ignore this email.
</Text>
</EmailLayout>
);
};

View file

@ -2,7 +2,7 @@ import { defaultEmailContext } from "./_components/email-context";
import NotificationEmail, {
NotificationBaseProps,
} from "./_components/notification-email";
import { Text } from "./_components/styled-components";
import { Heading, Text } from "./_components/styled-components";
export interface NewParticipantEmailProps extends NotificationBaseProps {
participantName: string;
@ -25,10 +25,12 @@ export const NewParticipantEmail = ({
disableNotificationsUrl={disableNotificationsUrl}
preview="Go to your poll to see the new response."
>
<Heading>New Response</Heading>
<Text>
<strong>{participantName}</strong> has responded to{" "}
<strong>{title}</strong>.
</Text>
<Text>Go to your poll to see the new response.</Text>
</NotificationEmail>
);
};

View file

@ -1,6 +1,12 @@
import { defaultEmailContext, EmailContext } from "./_components/email-context";
import { EmailLayout } from "./_components/email-layout";
import { Button, Card, Link, Text } from "./_components/styled-components";
import {
Button,
Card,
Heading,
Link,
Text,
} from "./_components/styled-components";
export interface NewPollEmailProps {
title: string;
@ -10,82 +16,28 @@ export interface NewPollEmailProps {
ctx: EmailContext;
}
const ShareLink = ({
title,
participantLink,
name,
children,
}: React.PropsWithChildren<{
name: string;
title: string;
participantLink: string;
}>) => {
return (
<Link
href={`mailto:?subject=${encodeURIComponent(
`Availability for ${title}`,
)}&body=${encodeURIComponent(
`Hi all,\nI'm trying to find the best date for ${title}.\nCan you please use the link below to choose your preferred dates:\n${participantLink}\nThank you.\n${name}`,
)}`}
>
{children}
</Link>
);
};
export const NewPollEmail = ({
title = "Untitled Poll",
name = "John",
adminLink = "https://rallly.co/admin/abcdefg123",
participantLink = "https://rallly.co/invite/wxyz9876",
ctx = defaultEmailContext,
}: NewPollEmailProps) => {
const { baseUrl, domain } = ctx;
return (
<EmailLayout
ctx={ctx}
footNote={
<>
You are receiving this email because a new poll was created with this
email address on <Link href={baseUrl}>{domain}</Link>. If this
wasn&apos;t you, please ignore this email.
</>
}
recipientName={name}
preview="Share your participant link to start collecting responses."
>
<Heading>New Poll Created</Heading>
<Text>
Your poll has been successfully created! Here are the details:
Your meeting poll titled <strong>{`"${title}"`}</strong> is ready! Share
it using the link below:
</Text>
<Card>
<Text>
<strong>Title:</strong> {title}
<br />
<strong>Invite Link:</strong>{" "}
<Card style={{ textAlign: "center" }}>
<Text style={{ textAlign: "center" }}>
<Link href={participantLink}>{participantLink}</Link>
</Text>
<Text>
<ShareLink
title={title}
name={name}
participantLink={participantLink}
>
Share via email
</ShareLink>
</Text>
</Card>
<Text>
To invite participants to your poll, simply share the{" "}
<strong>Invite Link</strong> above with them. They&apos;ll be able to
vote on their preferred meeting times and dates.
</Text>
<Text>
If you need to make any changes to your poll, or if you want to see the
results so far, just click on the button below:
</Text>
<Text>
<Button href={adminLink}>Manage Poll &rarr;</Button>
</Text>
<Button href={adminLink}>Manage Poll &rarr;</Button>
</EmailLayout>
);
};

View file

@ -1,6 +1,9 @@
import { Section } from "@react-email/section";
import { defaultEmailContext, EmailContext } from "./_components/email-context";
import { EmailLayout } from "./_components/email-layout";
import {
Card,
Domain,
Heading,
Text,
@ -17,24 +20,34 @@ export const RegisterEmail = ({
ctx = defaultEmailContext,
}: RegisterEmailProps) => {
return (
<EmailLayout
ctx={ctx}
footNote={
<>
You&apos;re receiving this email because a request was made to
register an account on <Domain ctx={ctx} />. If this wasn&apos;t you,
please ignore this email.
</>
}
preview={`Your 6-digit code is: ${code}`}
>
<EmailLayout ctx={ctx} preview={`Your 6-digit code is: ${code}`}>
<Heading>Verify your email address</Heading>
<Text>
Please use the following 6-digit verification code to verify your email:
</Text>
<Heading as="h1" style={{ ...trackingWide }} id="code">
{code}
</Heading>
<Text>This code is valid for 15 minutes</Text>
<Card style={{ textAlign: "center" }}>
<Text
style={{
...trackingWide,
textAlign: "center",
fontSize: "32px",
fontWeight: "bold",
}}
id="code"
>
{code}
</Text>
<Text style={{ textAlign: "center" }} light={true}>
This code is valid for 15 minutes
</Text>
</Card>
<Section>
<Text light={true}>
You&apos;re receiving this email because a request was made to
register an account on <Domain ctx={ctx} />. If this wasn&apos;t you,
please ignore this email.
</Text>
</Section>
</EmailLayout>
);
};