Update notification flow (#548)

This commit is contained in:
Luke Vella 2023-03-11 10:41:29 +00:00 committed by GitHub
parent cb1fb23b19
commit 39a07558ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 930 additions and 520 deletions

View file

@ -46,12 +46,19 @@ export const sendEmail = async <T extends TemplateName>(
templateName: T,
options: SendEmailOptions<T>,
) => {
if (!process.env.SUPPORT_EMAIL) {
console.info("SUPPORT_EMAIL not configured - skipping email send");
return;
}
const transport = getTransport();
const Template = templates[templateName] as TemplateComponent<T>;
try {
return await transport.sendMail({
from: process.env.SUPPORT_EMAIL,
from: {
name: "Rallly",
address: process.env.SUPPORT_EMAIL,
},
to: options.to,
subject: options.subject,
// eslint-disable-next-line @typescript-eslint/no-explicit-any

View file

@ -1,7 +1,7 @@
export * from "./templates/guest-verify-email";
export * from "./templates/login";
export * from "./templates/new-comment";
export * from "./templates/new-participant";
export * from "./templates/new-participant-confirmation";
export * from "./templates/new-poll";
export * from "./templates/new-poll-verification";
export * from "./templates/verification-code";
export * from "./templates/register";
export * from "./templates/turn-on-notifications";

View file

@ -4,6 +4,7 @@ import {
Body,
Container,
Head,
Hr,
Html,
Img,
Link,
@ -12,14 +13,24 @@ import {
} from "@react-email/components";
import { Tailwind } from "@react-email/tailwind";
export const EmailLayout = (props: {
children: React.ReactNode;
import { SmallText, Text } from "./styled-components";
interface EmailLayoutProps {
preview: string;
}) => {
recipientName: string;
footNote?: React.ReactNode;
}
export const EmailLayout = ({
preview,
recipientName = "Guest",
children,
footNote,
}: React.PropsWithChildren<EmailLayoutProps>) => {
return (
<Html>
<Head />
<Preview>{props.preview}</Preview>
<Preview>{preview}</Preview>
<Tailwind
config={{
theme: {
@ -115,12 +126,21 @@ export const EmailLayout = (props: {
},
}}
>
<Body className="bg-gray-50 p-4">
<Container className="mx-auto bg-white p-6">
<Body className="bg-white px-3 py-6">
<Container className="max-w-lg">
<Section className="mb-4">
<Img src={absoluteUrl("/logo.png")} alt="Rallly" width={128} />
</Section>
<Section>{props.children}</Section>
<Section>
<Text>Hi {recipientName},</Text>
{children}
{footNote ? (
<>
<Hr />
<SmallText>{footNote}</SmallText>
</>
) : null}
</Section>
<Section className="mt-4 text-sm text-slate-500">
<Link className="font-sans text-slate-500" href={absoluteUrl()}>
Home

View file

@ -1,6 +1,9 @@
import { absoluteUrl } from "@rallly/utils";
import { Hr } from "@react-email/components";
import { Container } from "@react-email/container";
import { Link, Text } from "./styled-components";
import { Link, SmallText, Text } from "./styled-components";
import { removeProtocalFromUrl } from "./utils";
export interface NewPollBaseEmailProps {
title: string;
@ -29,6 +32,12 @@ export const NewPollBaseEmail = ({
</Link>
</Text>
{children}
<Hr />
<SmallText>
You are receiving this email because a new poll was created with this
email address on{" "}
<Link href={absoluteUrl()}>{removeProtocalFromUrl(absoluteUrl())}</Link>
</SmallText>
</Container>
);
};

View file

@ -0,0 +1,47 @@
import { EmailLayout } from "./email-layout";
import { Link, Section } from "./styled-components";
export interface NotificationBaseProps {
name: string;
title: string;
pollUrl: string;
unsubscribeUrl: string;
}
export interface NotificationEmailProps extends NotificationBaseProps {
preview: string;
}
export const NotificationEmail = ({
name,
title,
pollUrl,
unsubscribeUrl,
preview,
children,
}: React.PropsWithChildren<NotificationEmailProps>) => {
return (
<EmailLayout
recipientName={name}
footNote={
<>
You&apos;re receiving this email because notifications are enabled for{" "}
<strong>{title}</strong>. If you want to stop receiving emails about
this event you can{" "}
<Link className="whitespace-nowrap" href={unsubscribeUrl}>
turn notifications off
</Link>
.
</>
}
preview={preview}
>
{children}
<Section>
<Link href={pollUrl}>Go to poll &rarr;</Link>
</Section>
</EmailLayout>
);
};
export default NotificationEmail;

View file

@ -39,7 +39,7 @@ export const Link = (props: LinkProps) => {
return (
<UnstyledLink
{...props}
className={clsx("text-primary-500 font-sans text-base", props.className)}
className={clsx("text-primary-500", props.className)}
/>
);
};
@ -61,3 +61,12 @@ export const Section = (props: SectionProps) => {
<UnstyledSection {...props} className={clsx("my-4", props.className)} />
);
};
export const SmallText = (props: TextProps) => {
return (
<UnstyledText
{...props}
className={clsx("font-sans text-sm text-slate-500", props.className)}
/>
);
};

View file

@ -0,0 +1,3 @@
export const removeProtocalFromUrl = (url: string) => {
return url.replace(/(^\w+:|^)\/\//, "");
};

View file

@ -1,41 +0,0 @@
import { Button, Container } from "@react-email/components";
import { EmailLayout } from "./components/email-layout";
import { Section, Text } from "./components/styled-components";
type GuestVerifyEmailProps = {
title: string;
name: string;
verificationLink: string;
adminLink: string;
};
export const GuestVerifyEmail = ({
title = "Untitled Poll",
name = "Guest",
verificationLink = "https://rallly.co",
}: GuestVerifyEmailProps) => {
return (
<EmailLayout preview="Click the button below to verify your email">
<Container>
<Text>Hi {name},</Text>
<Text>
To receive notifications for <strong>&quot;{title}&quot;</strong> you
will need to verify your email address.
</Text>
<Text>To verify your email please click the button below.</Text>
<Section>
<Button
className="bg-primary-500 rounded px-3 py-2 font-sans text-white"
href={verificationLink}
id="verifyEmailUrl"
>
Verify your email &rarr;
</Button>
</Section>
</Container>
</EmailLayout>
);
};
export default GuestVerifyEmail;

View file

@ -0,0 +1,55 @@
import { absoluteUrl } from "@rallly/utils";
import { EmailLayout } from "./components/email-layout";
import { Heading, Link, Text } from "./components/styled-components";
import { removeProtocalFromUrl } from "./components/utils";
interface LoginEmailProps {
name: string;
code: string;
// magicLink: string;
}
export const LoginEmail = ({
name = "Guest",
code = "123456",
}: // magicLink = "https://rallly.co",
LoginEmailProps) => {
return (
<EmailLayout
footNote={
<>
You&apos;re receiving this email because a request was made to login
to{" "}
<Link href={absoluteUrl()}>
{removeProtocalFromUrl(absoluteUrl())}
</Link>
. If this wasn&apos;t you, let us know by replying to this email.
</>
}
recipientName={name}
preview={`Your 6-digit code: ${code}`}
>
<Text>Your 6-digit code is:</Text>
<Heading as="h1" className="font-sans tracking-widest" id="code">
{code}
</Heading>
<Text>
Use this code to complete the verification process on{" "}
<Link href={absoluteUrl()}>{removeProtocalFromUrl(absoluteUrl())}</Link>
</Text>
<Text>
<span className="text-slate-500">
This code is valid for 15 minutes
</span>
</Text>
{/* <Heading>Magic link</Heading>
<Text>
Alternatively, you can login by using this{" "}
<Link href={magicLink}>magic link </Link>
</Text> */}
</EmailLayout>
);
};
export default LoginEmail;

View file

@ -1,12 +1,10 @@
import { EmailLayout } from "./components/email-layout";
import { Button, Link, Section, Text } from "./components/styled-components";
import NotificationEmail, {
NotificationBaseProps,
} from "./components/notification-email";
import { Text } from "./components/styled-components";
export interface NewCommentEmailProps {
name: string;
title: string;
export interface NewCommentEmailProps extends NotificationBaseProps {
authorName: string;
pollUrl: string;
unsubscribeUrl: string;
}
export const NewCommentEmail = ({
@ -17,20 +15,17 @@ export const NewCommentEmail = ({
unsubscribeUrl = "https://rallly.co",
}: NewCommentEmailProps) => {
return (
<EmailLayout preview={`${authorName} has commented on ${title}`}>
<Text>Hi {name},</Text>
<NotificationEmail
name={name}
title={title}
pollUrl={pollUrl}
unsubscribeUrl={unsubscribeUrl}
preview={`${authorName} has commented on ${title}`}
>
<Text>
<strong>{authorName}</strong> has commented on <strong>{title}</strong>.
</Text>
<Section>
<Button href={pollUrl}>Go to poll &rarr;</Button>
</Section>
<Text>
<Link href={unsubscribeUrl}>
Stop receiving notifications for this poll.
</Link>
</Text>
</EmailLayout>
</NotificationEmail>
);
};

View file

@ -12,8 +12,13 @@ export const NewParticipantConfirmationEmail = ({
editSubmissionUrl = "https://rallly.co",
}: NewParticipantConfirmationEmailProps) => {
return (
<EmailLayout preview="To edit your response use the link below">
<Text>Hi {name},</Text>
<EmailLayout
footNote={
<>You are receiving this email because a response was submitting </>
}
recipientName={name}
preview="To edit your response use the link below"
>
<Text>
Thank you for submitting your availability for <strong>{title}</strong>.
</Text>

View file

@ -1,12 +1,10 @@
import { EmailLayout } from "./components/email-layout";
import { Button, Link, Section, Text } from "./components/styled-components";
import NotificationEmail, {
NotificationBaseProps,
} from "./components/notification-email";
import { Text } from "./components/styled-components";
export interface NewParticipantEmailProps {
name: string;
title: string;
export interface NewParticipantEmailProps extends NotificationBaseProps {
participantName: string;
pollUrl: string;
unsubscribeUrl: string;
}
export const NewParticipantEmail = ({
@ -17,21 +15,18 @@ export const NewParticipantEmail = ({
unsubscribeUrl = "https://rallly.co",
}: NewParticipantEmailProps) => {
return (
<EmailLayout preview={`${participantName} has responded`}>
<Text>Hi {name},</Text>
<NotificationEmail
name={name}
title={title}
pollUrl={pollUrl}
unsubscribeUrl={unsubscribeUrl}
preview={`${participantName} has responded`}
>
<Text>
<strong>{participantName}</strong> has shared their availability for{" "}
<strong>{participantName}</strong> has responded to{" "}
<strong>{title}</strong>.
</Text>
<Section>
<Button href={pollUrl}>Go to poll &rarr;</Button>
</Section>
<Text>
<Link href={unsubscribeUrl}>
Stop receiving notifications for this poll.
</Link>
</Text>
</EmailLayout>
</NotificationEmail>
);
};

View file

@ -1,37 +0,0 @@
import { EmailLayout } from "./components/email-layout";
import {
NewPollBaseEmail,
NewPollBaseEmailProps,
} from "./components/new-poll-base";
import { Button, Heading, Section, Text } from "./components/styled-components";
export interface NewPollVerificationEmailProps extends NewPollBaseEmailProps {
verificationLink: string;
}
export const NewPollVerificationEmail = ({
title = "Untitled Poll",
name = "Guest",
verificationLink = "https://rallly.co",
adminLink = "https://rallly.co/admin/abcdefg123",
}: NewPollVerificationEmailProps) => {
return (
<EmailLayout preview="Please verify your email address to turn on notifications">
<NewPollBaseEmail name={name} title={title} adminLink={adminLink}>
<Section className="mt-8 bg-gray-100 px-4 text-center">
<Heading as="h3">
Want to get notified when participants vote?
</Heading>
<Text>Verify your email address to turn on notifications.</Text>
<Section>
<Button id="verifyEmailUrl" href={verificationLink}>
Verify your email &rarr;
</Button>
</Section>
</Section>
</NewPollBaseEmail>
</EmailLayout>
);
};
export default NewPollVerificationEmail;

View file

@ -1,17 +1,93 @@
import { absoluteUrl } from "@rallly/utils";
import { EmailLayout } from "./components/email-layout";
import {
NewPollBaseEmail,
NewPollBaseEmailProps,
} from "./components/new-poll-base";
import { Heading, Link, Section, Text } from "./components/styled-components";
import { removeProtocalFromUrl } from "./components/utils";
export interface NewPollEmailProps {
title: string;
name: string;
adminLink: string;
participantLink: string;
}
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>
);
};
const LinkContainer = (props: { link: string }) => {
return (
<Section className="rounded bg-gray-50 p-4">
<Link href={props.link} className="font-mono">
{props.link}
</Link>
</Section>
);
};
export const NewPollEmail = ({
title = "Untitled Poll",
name = "Guest",
adminLink = "https://rallly.co/admin/abcdefg123",
}: NewPollBaseEmailProps) => {
participantLink = "https://rallly.co/p/wxyz9876",
}: NewPollEmailProps) => {
return (
<EmailLayout preview="Please verify your email address to turn on notifications">
<NewPollBaseEmail name={name} title={title} adminLink={adminLink} />
<EmailLayout
footNote={
<>
You are receiving this email because a new poll was created with this
email address on{" "}
<Link href={absoluteUrl()}>
{removeProtocalFromUrl(absoluteUrl())}
</Link>
. If this wasn&apos;t you, please ignore this email.
</>
}
recipientName={name}
preview="Share your participant link to start collecting responses."
>
<Text>
Your new poll is ready! Now lets find a date for{" "}
<strong>{title}</strong>.
</Text>
<Text>
Copy this link and share it with your participants to start collecting
responses.
</Text>
<LinkContainer link={participantLink} />
<Text>
<ShareLink title={title} name={name} participantLink={participantLink}>
Share via email &rarr;
</ShareLink>
</Text>
<Heading>Your secret link</Heading>
<Text>
Use this link to access the admin page where you can view and edit your
poll.
</Text>
<LinkContainer link={adminLink} />
<Text>
<Link href={adminLink}>Go to admin page &rarr;</Link>
</Text>
</EmailLayout>
);
};

View file

@ -0,0 +1,47 @@
import { absoluteUrl } from "@rallly/utils";
import { Heading } from "@react-email/heading";
import { EmailLayout } from "./components/email-layout";
import { Link, Text } from "./components/styled-components";
import { removeProtocalFromUrl } from "./components/utils";
interface RegisterEmailProps {
name: string;
code: string;
}
export const RegisterEmail = ({
name = "Guest",
code = "123456",
}: RegisterEmailProps) => {
return (
<EmailLayout
footNote={
<>
You&apos;re receiving this email because a request was made to
register an account on{" "}
<Link className="text-primary-500" href={absoluteUrl()}>
{removeProtocalFromUrl(absoluteUrl())}
</Link>
.
</>
}
recipientName={name}
preview={`Your 6-digit code is: ${code}`}
>
<Text>Your 6-digit code is:</Text>
<Heading className="font-sans tracking-widest" id="code">
{code}
</Heading>
<Text>
Use this code to complete the verification process on{" "}
<Link href={absoluteUrl()}>{removeProtocalFromUrl(absoluteUrl())}</Link>
</Text>
<Text>
<span className="text-gray-500">This code is valid for 15 minutes</span>
</Text>
</EmailLayout>
);
};
export default RegisterEmail;

View file

@ -0,0 +1,51 @@
import { EmailLayout } from "./components/email-layout";
import {
Button,
Link,
Section,
SmallText,
Text,
} from "./components/styled-components";
type EnableNotificationsEmailProps = {
title: string;
name: string;
verificationLink: string;
adminLink: string;
};
export const EnableNotificationsEmail = ({
title = "Untitled Poll",
name = "Guest",
verificationLink = "https://rallly.co",
adminLink = "https://rallly.co",
}: EnableNotificationsEmailProps) => {
return (
<EmailLayout
recipientName={name}
preview="Before we can send you notifications we need to verify your email"
footNote={
<>
You are receiving this email because a request was made to enable
notifications for <Link href={adminLink}>{title}</Link>.
</>
}
>
<Text>
Before we can send you notifications we need to verify your email.
</Text>
<Text>
Click the button below to complete the email verification and enable
notifications for <strong>{title}</strong>.
</Text>
<Section>
<Button href={verificationLink} id="verifyEmailUrl">
Enable notifications &rarr;
</Button>
</Section>
<SmallText>The link will expire in 15 minutes.</SmallText>
</EmailLayout>
);
};
export default EnableNotificationsEmail;

View file

@ -1,34 +0,0 @@
import { Heading } from "@react-email/heading";
import { EmailLayout } from "./components/email-layout";
import { Section, Text } from "./components/styled-components";
interface VerificationCodeEmailProps {
name: string;
code: string;
}
export const VerificationCodeEmail = ({
name = "Guest",
code = "123456",
}: VerificationCodeEmailProps) => {
return (
<EmailLayout preview={`Your 6-digit code is ${code}`}>
<Text>Hi {name},</Text>
<Text>Please use the code below to verify your email address.</Text>
<Section className="rounded bg-gray-50 text-center">
<Text>Your 6-digit code is:</Text>
<Heading className="font-sans tracking-widest" id="code">
{code}
</Heading>
<Text>
<span className="text-slate-500">
This code is valid for 15 minutes
</span>
</Text>
</Section>
</EmailLayout>
);
};
export default VerificationCodeEmail;