mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-17 08:17:26 +02:00
⚡️ Improve email rendering speed (#654)
This commit is contained in:
parent
c6e61c3a72
commit
42794fedd1
9 changed files with 319 additions and 329 deletions
|
@ -87,6 +87,7 @@ export const sendEmail = async <T extends TemplateName>(
|
|||
}
|
||||
|
||||
const Template = templates[templateName] as TemplateComponent<T>;
|
||||
const html = render(<Template {...(options.props as any)} />);
|
||||
|
||||
try {
|
||||
await sendRawEmail({
|
||||
|
@ -97,7 +98,7 @@ export const sendEmail = async <T extends TemplateName>(
|
|||
to: options.to,
|
||||
subject: options.subject,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
html: render(<Template {...(options.props as any)} />),
|
||||
html,
|
||||
});
|
||||
return;
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
import tailwindConfig from "@rallly/tailwind-config";
|
||||
import { absoluteUrl } from "@rallly/utils";
|
||||
import {
|
||||
Body,
|
||||
Container,
|
||||
Head,
|
||||
Hr,
|
||||
Html,
|
||||
Img,
|
||||
Link,
|
||||
Preview,
|
||||
Section,
|
||||
} from "@react-email/components";
|
||||
import { Tailwind } from "@react-email/tailwind";
|
||||
|
||||
import { Text } from "./styled-components";
|
||||
import { fontFamily, Section, Text } from "./styled-components";
|
||||
|
||||
interface EmailLayoutProps {
|
||||
preview: string;
|
||||
|
@ -21,6 +17,22 @@ interface EmailLayoutProps {
|
|||
footNote?: React.ReactNode;
|
||||
}
|
||||
|
||||
const containerStyles = {
|
||||
maxWidth: "600px",
|
||||
margin: "0 auto",
|
||||
fontFamily,
|
||||
};
|
||||
|
||||
const sectionStyles = {
|
||||
marginTop: "16px",
|
||||
marginBottom: "16px",
|
||||
};
|
||||
|
||||
const linkStyles = {
|
||||
color: "#64748B",
|
||||
marginRight: "8px",
|
||||
};
|
||||
|
||||
export const EmailLayout = ({
|
||||
preview,
|
||||
recipientName = "Guest",
|
||||
|
@ -32,152 +44,45 @@ export const EmailLayout = ({
|
|||
<Html>
|
||||
<Head />
|
||||
<Preview>{preview}</Preview>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
...tailwindConfig.theme.extend,
|
||||
spacing: {
|
||||
screen: "100vw",
|
||||
full: "100%",
|
||||
px: "1px",
|
||||
0: "0",
|
||||
0.5: "2px",
|
||||
1: "4px",
|
||||
1.5: "6px",
|
||||
2: "8px",
|
||||
2.5: "10px",
|
||||
3: "12px",
|
||||
3.5: "14px",
|
||||
4: "16px",
|
||||
4.5: "18px",
|
||||
5: "20px",
|
||||
5.5: "22px",
|
||||
6: "24px",
|
||||
6.5: "26px",
|
||||
7: "28px",
|
||||
7.5: "30px",
|
||||
8: "32px",
|
||||
8.5: "34px",
|
||||
9: "36px",
|
||||
9.5: "38px",
|
||||
10: "40px",
|
||||
11: "44px",
|
||||
12: "48px",
|
||||
14: "56px",
|
||||
16: "64px",
|
||||
20: "80px",
|
||||
24: "96px",
|
||||
28: "112px",
|
||||
32: "128px",
|
||||
36: "144px",
|
||||
40: "160px",
|
||||
44: "176px",
|
||||
48: "192px",
|
||||
52: "208px",
|
||||
56: "224px",
|
||||
60: "240px",
|
||||
64: "256px",
|
||||
72: "288px",
|
||||
80: "320px",
|
||||
96: "384px",
|
||||
97.5: "390px",
|
||||
120: "480px",
|
||||
150: "600px",
|
||||
160: "640px",
|
||||
175: "700px",
|
||||
"1/2": "50%",
|
||||
"1/3": "33.333333%",
|
||||
"2/3": "66.666667%",
|
||||
"1/4": "25%",
|
||||
"2/4": "50%",
|
||||
"3/4": "75%",
|
||||
"1/5": "20%",
|
||||
"2/5": "40%",
|
||||
"3/5": "60%",
|
||||
"4/5": "80%",
|
||||
"1/6": "16.666667%",
|
||||
"2/6": "33.333333%",
|
||||
"3/6": "50%",
|
||||
"4/6": "66.666667%",
|
||||
"5/6": "83.333333%",
|
||||
"1/12": "8.333333%",
|
||||
"2/12": "16.666667%",
|
||||
"3/12": "25%",
|
||||
"4/12": "33.333333%",
|
||||
"5/12": "41.666667%",
|
||||
"6/12": "50%",
|
||||
"7/12": "58.333333%",
|
||||
"8/12": "66.666667%",
|
||||
"9/12": "75%",
|
||||
"10/12": "83.333333%",
|
||||
"11/12": "91.666667%",
|
||||
},
|
||||
borderRadius: {
|
||||
none: "0px",
|
||||
sm: "2px",
|
||||
DEFAULT: "4px",
|
||||
md: "6px",
|
||||
lg: "8px",
|
||||
xl: "12px",
|
||||
"2xl": "16px",
|
||||
"3xl": "24px",
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Body className="bg-white p-3">
|
||||
<Container className="max-w-xl">
|
||||
<Section className="my-4">
|
||||
<Img src={absoluteUrl("/logo.png")} alt="Rallly" width={128} />
|
||||
</Section>
|
||||
<Section>
|
||||
<Text>Hi {firstName},</Text>
|
||||
{children}
|
||||
{footNote ? (
|
||||
<>
|
||||
<Hr />
|
||||
<Text light={true}>{footNote}</Text>
|
||||
</>
|
||||
) : null}
|
||||
</Section>
|
||||
<Section className="mt-4 text-sm text-slate-500">
|
||||
<Link className="font-sans text-slate-500" href={absoluteUrl()}>
|
||||
Home
|
||||
</Link>
|
||||
•
|
||||
<Link
|
||||
className="font-sans text-slate-500"
|
||||
href="https://twitter.com/ralllyco"
|
||||
>
|
||||
Twitter
|
||||
</Link>
|
||||
•
|
||||
<Link
|
||||
className="font-sans text-slate-500"
|
||||
href="https://github.com/lukevella/rallly"
|
||||
>
|
||||
Github
|
||||
</Link>
|
||||
•
|
||||
<Link
|
||||
className="font-sans text-slate-500"
|
||||
href="https://www.paypal.com/donate/?hosted_button_id=7QXP2CUBLY88E"
|
||||
>
|
||||
Donate
|
||||
</Link>
|
||||
•
|
||||
<Link
|
||||
className="font-sans text-slate-500"
|
||||
href={`mailto:${process.env.SUPPORT_EMAIL}`}
|
||||
>
|
||||
Contact
|
||||
</Link>
|
||||
</Section>
|
||||
</Container>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
<Body style={{ backgroundColor: "white", padding: "16px" }}>
|
||||
<Container style={containerStyles}>
|
||||
<Img src={absoluteUrl("/logo.png")} alt="Rallly" width={128} />
|
||||
<Section style={sectionStyles}>
|
||||
<Text>Hi {firstName},</Text>
|
||||
{children}
|
||||
{footNote ? (
|
||||
<Text style={{ color: "#64748B", fontFamily }}>{footNote}</Text>
|
||||
) : null}
|
||||
</Section>
|
||||
<Section style={{ ...sectionStyles, fontSize: 14 }}>
|
||||
<Link style={linkStyles} href={absoluteUrl()}>
|
||||
Home
|
||||
</Link>
|
||||
<span> • </span>
|
||||
<Link style={linkStyles} href="https://twitter.com/ralllyco">
|
||||
Twitter
|
||||
</Link>
|
||||
<span> • </span>
|
||||
<Link style={linkStyles} href="https://github.com/lukevella/rallly">
|
||||
Github
|
||||
</Link>
|
||||
<span> • </span>
|
||||
<Link
|
||||
style={linkStyles}
|
||||
href="https://www.paypal.com/donate/?hosted_button_id=7QXP2CUBLY88E"
|
||||
>
|
||||
Donate
|
||||
</Link>
|
||||
<span> • </span>
|
||||
<Link
|
||||
style={linkStyles}
|
||||
href={`mailto:${process.env.SUPPORT_EMAIL}`}
|
||||
>
|
||||
Contact
|
||||
</Link>
|
||||
</Section>
|
||||
</Container>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
import { absoluteUrl } from "@rallly/utils";
|
||||
import { Hr } from "@react-email/components";
|
||||
import { Container } from "@react-email/container";
|
||||
|
||||
import { Link, SmallText, Text } from "./styled-components";
|
||||
import { removeProtocalFromUrl } from "./utils";
|
||||
|
||||
export interface NewPollBaseEmailProps {
|
||||
title: string;
|
||||
name: string;
|
||||
adminLink: string;
|
||||
}
|
||||
|
||||
export const NewPollBaseEmail = ({
|
||||
name,
|
||||
title,
|
||||
adminLink,
|
||||
children,
|
||||
}: React.PropsWithChildren<NewPollBaseEmailProps>) => {
|
||||
return (
|
||||
<Container>
|
||||
<Text>Hi {name},</Text>
|
||||
<Text>
|
||||
Your poll <strong>"{title}"</strong> has been created.
|
||||
</Text>
|
||||
<Text>
|
||||
To manage your poll use the <em>admin link</em> below.
|
||||
</Text>
|
||||
<Text>
|
||||
<Link href={adminLink}>
|
||||
<span className="font-mono">{adminLink}</span> →
|
||||
</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>
|
||||
);
|
||||
};
|
|
@ -10,23 +10,24 @@ import {
|
|||
Text as UnstyledText,
|
||||
TextProps,
|
||||
} from "@react-email/components";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { getDomain } from "./utils";
|
||||
|
||||
export const Text = (
|
||||
props: TextProps & { light?: boolean; small?: boolean },
|
||||
) => {
|
||||
const { light, small, className, ...forwardProps } = props;
|
||||
const { light, small, ...forwardProps } = props;
|
||||
return (
|
||||
<UnstyledText
|
||||
{...forwardProps}
|
||||
className={clsx(
|
||||
"my-4 font-sans ",
|
||||
{ "text-base": !small, "text-sm": small },
|
||||
{ "text-slate-800": !light, "text-slate-500": light },
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
margin: "16px 0",
|
||||
fontFamily,
|
||||
fontSize: small ? "14px" : "16px",
|
||||
color: light ? "#64748B" : "#374151",
|
||||
lineHeight: "1.5",
|
||||
...props.style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -39,10 +40,15 @@ export const Button = (props: ButtonProps) => {
|
|||
return (
|
||||
<UnstyledButton
|
||||
{...props}
|
||||
className={clsx(
|
||||
"bg-primary-600 rounded px-3 py-2 font-sans text-white",
|
||||
props.className,
|
||||
)}
|
||||
className={props.className}
|
||||
style={{
|
||||
backgroundColor: "#4F46E5",
|
||||
borderRadius: "4px",
|
||||
padding: "8px 12px",
|
||||
fontFamily,
|
||||
fontSize: "16px",
|
||||
color: "white",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -51,7 +57,7 @@ export const Link = (props: LinkProps) => {
|
|||
return (
|
||||
<UnstyledLink
|
||||
{...props}
|
||||
className={clsx("text-primary-600", props.className)}
|
||||
style={{ color: "#4F46E5", fontFamily, ...props.style }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -60,35 +66,53 @@ 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",
|
||||
};
|
||||
return (
|
||||
<UnstyledHeading
|
||||
{...props}
|
||||
as={as}
|
||||
className={clsx(
|
||||
"mt-4 mb-2 font-sans font-semibold text-slate-800",
|
||||
props.className,
|
||||
)}
|
||||
style={{
|
||||
marginTop: "16px",
|
||||
marginBottom: "8px",
|
||||
fontFamily: "sans-serif",
|
||||
fontWeight: "bold",
|
||||
fontSize: fontSize[as],
|
||||
color: "#1E293B",
|
||||
...props.style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SubHeadingText = (props: TextProps) => {
|
||||
const { className, ...forwardProps } = props;
|
||||
return (
|
||||
<UnstyledText
|
||||
{...forwardProps}
|
||||
className={clsx(
|
||||
"mb-4 mt-2 font-sans text-base text-slate-800",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
style={{
|
||||
marginBottom: "16px",
|
||||
marginTop: "8px",
|
||||
fontSize: 16,
|
||||
color: "#374151",
|
||||
fontFamily,
|
||||
...props.style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Section = (props: SectionProps) => {
|
||||
const { className, ...forwardProps } = props;
|
||||
return (
|
||||
<UnstyledSection {...forwardProps} className={clsx("my-4", className)} />
|
||||
<UnstyledSection
|
||||
{...props}
|
||||
style={{ marginTop: "16px", marginBottom: "16px", ...props.style }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -96,7 +120,11 @@ export const SmallText = (props: TextProps) => {
|
|||
return (
|
||||
<UnstyledText
|
||||
{...props}
|
||||
className={clsx("font-sans text-sm text-slate-500", props.className)}
|
||||
style={{
|
||||
fontSize: "14px",
|
||||
color: "#6B7280",
|
||||
...props.style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -105,7 +133,20 @@ export const Card = (props: SectionProps) => {
|
|||
return (
|
||||
<Section
|
||||
{...props}
|
||||
className={clsx("rounded bg-gray-50 px-4", props.className)}
|
||||
style={{
|
||||
borderRadius: "4px",
|
||||
backgroundColor: "#F1F5F9",
|
||||
paddingRight: "16px",
|
||||
paddingLeft: "16px",
|
||||
border: "1px solid #E2E8F0",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const trackingWide = {
|
||||
letterSpacing: 2,
|
||||
};
|
||||
|
||||
export const fontFamily =
|
||||
"'Inter UI', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;";
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
Domain,
|
||||
Heading,
|
||||
Text,
|
||||
trackingWide,
|
||||
} from "./components/styled-components";
|
||||
import { getDomain } from "./components/utils";
|
||||
|
||||
|
@ -45,7 +46,7 @@ export const LoginEmail = ({
|
|||
<Card>
|
||||
<Heading>Option 2: Verification Code</Heading>
|
||||
<Text>Enter this one-time 6-digit verification code.</Text>
|
||||
<Heading as="h1" className="tracking-widest" id="code">
|
||||
<Heading as="h1" style={trackingWide} id="code">
|
||||
{code}
|
||||
</Heading>
|
||||
<Text light={true}>This code will expire in 15 minutes.</Text>
|
||||
|
|
|
@ -41,6 +41,23 @@ const ShareLink = ({
|
|||
);
|
||||
};
|
||||
|
||||
const LinkContainer = (props: { href: string }) => {
|
||||
return (
|
||||
<Text
|
||||
style={{
|
||||
borderRadius: "4px",
|
||||
backgroundColor: "white",
|
||||
padding: "12px",
|
||||
border: "1px solid #E2E8F0",
|
||||
}}
|
||||
>
|
||||
<Link href={props.href} style={{ letterSpacing: 1 }}>
|
||||
{props.href}
|
||||
</Link>
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
export const NewPollEmail = ({
|
||||
title = "Untitled Poll",
|
||||
name = "John",
|
||||
|
@ -60,18 +77,15 @@ export const NewPollEmail = ({
|
|||
preview="Share your participant link to start collecting responses."
|
||||
>
|
||||
<Text>
|
||||
Your poll is live! Here are two links you will need to manage your poll.
|
||||
Your poll for <strong>{title}</strong> is live! Here are two links you
|
||||
will need to manage your poll.
|
||||
</Text>
|
||||
<Card>
|
||||
<Heading>Admin link</Heading>
|
||||
<SubHeadingText>
|
||||
Use this link to view results and make changes to your poll.
|
||||
</SubHeadingText>
|
||||
<Text className="rounded bg-white px-4 py-3">
|
||||
<Link href={adminLink} className="font-mono">
|
||||
{adminLink}
|
||||
</Link>
|
||||
</Text>
|
||||
<LinkContainer href={adminLink} />
|
||||
<Text>
|
||||
<Button href={adminLink}>Go to admin page</Button>
|
||||
</Text>
|
||||
|
@ -82,11 +96,7 @@ export const NewPollEmail = ({
|
|||
Copy this link and share it with your participants to start collecting
|
||||
responses.
|
||||
</SubHeadingText>
|
||||
<Text className="rounded bg-white px-4 py-3">
|
||||
<Link href={participantLink} className="font-mono">
|
||||
{participantLink}
|
||||
</Link>
|
||||
</Text>
|
||||
<LinkContainer href={participantLink} />
|
||||
<Text>
|
||||
<ShareLink
|
||||
title={title}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { EmailLayout } from "./components/email-layout";
|
||||
import { Domain, Heading, Text } from "./components/styled-components";
|
||||
import {
|
||||
Domain,
|
||||
Heading,
|
||||
Text,
|
||||
trackingWide,
|
||||
} from "./components/styled-components";
|
||||
|
||||
interface RegisterEmailProps {
|
||||
name: string;
|
||||
|
@ -25,7 +30,7 @@ export const RegisterEmail = ({
|
|||
<Text>
|
||||
Please use the following 6-digit verification code to verify your email:
|
||||
</Text>
|
||||
<Heading as="h1" className="font-sans tracking-widest" id="code">
|
||||
<Heading as="h1" style={{ ...trackingWide }} id="code">
|
||||
{code}
|
||||
</Heading>
|
||||
<Text>This code is valid for 15 minutes</Text>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue