♻️ Update react-email package (#1001)

This commit is contained in:
Luke Vella 2024-01-28 16:19:36 +07:00 committed by GitHub
parent 317244ef28
commit 7e213b0b4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 2493 additions and 1913 deletions

View file

@ -39,19 +39,10 @@
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "^12.3.4", "@next/bundle-analyzer": "^12.3.4",
"@rallly/tsconfig": "*", "@rallly/tsconfig": "*",
"@types/accept-language-parser": "^1.5.3",
"@types/color-hash": "^1.0.2", "@types/color-hash": "^1.0.2",
"@types/lodash": "^4.14.178", "@types/lodash": "^4.14.178",
"@types/react": "^18.0.28",
"@types/react-big-calendar": "^0.31.0",
"@types/react-dom": "^18.0.11",
"@types/react-linkify": "^1.0.1",
"@types/smoothscroll-polyfill": "^0.3.1",
"cheerio": "^1.0.0-rc.12",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"i18next-scanner": "^4.2.0", "i18next-scanner": "^4.2.0",
"i18next-scanner-typescript": "^1.1.1", "i18next-scanner-typescript": "^1.1.1"
"smtp-tester": "^2.0.1",
"wait-on": "^6.0.1"
} }
} }

View file

@ -68,7 +68,8 @@
"react-hook-form-persist": "^3.0.0", "react-hook-form-persist": "^3.0.0",
"react-hot-toast": "^2.4.0", "react-hot-toast": "^2.4.0",
"react-i18next": "^12.1.4", "react-i18next": "^12.1.4",
"react-linkify": "^1.0.0-alpha", "linkifyjs": "^4.1.3",
"linkify-react": "^4.1.3",
"react-remove-scroll": "^2.5.6", "react-remove-scroll": "^2.5.6",
"react-use": "^17.4.0", "react-use": "^17.4.0",
"smoothscroll-polyfill": "^0.4.4", "smoothscroll-polyfill": "^0.4.4",
@ -83,10 +84,7 @@
"@types/accept-language-parser": "^1.5.3", "@types/accept-language-parser": "^1.5.3",
"@types/color-hash": "^1.0.2", "@types/color-hash": "^1.0.2",
"@types/lodash": "^4.14.178", "@types/lodash": "^4.14.178",
"@types/react": "^18.0.28", "@types/react-big-calendar": "^1.8.8",
"@types/react-big-calendar": "^0.31.0",
"@types/react-dom": "^18.0.11",
"@types/react-linkify": "^1.0.1",
"@types/smoothscroll-polyfill": "^0.3.1", "@types/smoothscroll-polyfill": "^0.3.1",
"cheerio": "^1.0.0-rc.12", "cheerio": "^1.0.0-rc.12",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",

View file

@ -2,16 +2,18 @@ import { AnimatePresence, m } from "framer-motion";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import Link from "next/link"; import Link from "next/link";
import * as React from "react"; import * as React from "react";
import ReactDOM from "react-dom"; import { createPortal } from "react-dom";
import { getPortal } from "@/utils/selectors"; import { getPortal } from "@/utils/selectors";
import CookiesIllustration from "./cookie-consent/cookies.svg"; import CookiesIllustration from "./cookie-consent/cookies.svg";
const CookieConsentPopover: React.FunctionComponent = () => { const CookieConsentPopover = () => {
const [visible, setVisible] = React.useState(true); const [visible, setVisible] = React.useState(true);
return ReactDOM.createPortal( return (
<>
{createPortal(
<AnimatePresence> <AnimatePresence>
{visible ? ( {visible ? (
<m.div <m.div
@ -34,8 +36,8 @@ const CookieConsentPopover: React.FunctionComponent = () => {
> >
<CookiesIllustration className="absolute -top-6" /> <CookiesIllustration className="absolute -top-6" />
<div className="mb-3"> <div className="mb-3">
Your privacy is important to us. We only use cookies to improve the Your privacy is important to us. We only use cookies to improve
browsing experience on this website. the browsing experience on this website.
</div> </div>
<div className="flex items-center space-x-6"> <div className="flex items-center space-x-6">
<Link <Link
@ -58,6 +60,8 @@ const CookieConsentPopover: React.FunctionComponent = () => {
) : null} ) : null}
</AnimatePresence>, </AnimatePresence>,
getPortal(), getPortal(),
)}
</>
); );
}; };

View file

@ -4,7 +4,7 @@ import "./rbc-overrides.css";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { XIcon } from "lucide-react"; import { XIcon } from "lucide-react";
import React from "react"; import React from "react";
import { Calendar } from "react-big-calendar"; import { Calendar, CalendarProps } from "react-big-calendar";
import { createBreakpoint } from "react-use"; import { createBreakpoint } from "react-use";
import { getDuration } from "../../../utils/date-time-utils"; import { getDuration } from "../../../utils/date-time-utils";
@ -17,6 +17,12 @@ const localizer = dayjsLocalizer(dayjs);
const useDevice = createBreakpoint({ desktop: 720, mobile: 360 }); const useDevice = createBreakpoint({ desktop: 720, mobile: 360 });
/**
* Not sure what's wrong with the type definitions for react-big-calendar but it's not working properly.
* This is a temporary fix that overrides their types which ideally we wouldn't have to do.
*/
const CalendarTempFix = Calendar as React.ComponentType<CalendarProps>;
const WeekCalendar: React.FunctionComponent<DateTimePickerProps> = ({ const WeekCalendar: React.FunctionComponent<DateTimePickerProps> = ({
options, options,
onNavigate, onNavigate,
@ -36,7 +42,7 @@ const WeekCalendar: React.FunctionComponent<DateTimePickerProps> = ({
return ( return (
<div className="relative flex h-[600px]"> <div className="relative flex h-[600px]">
<Calendar <CalendarTempFix
className="absolute inset-0" className="absolute inset-0"
events={options.map((option) => { events={options.map((option) => {
if (option.type === "date") { if (option.type === "date") {
@ -61,6 +67,7 @@ const WeekCalendar: React.FunctionComponent<DateTimePickerProps> = ({
(option) => (option) =>
!( !(
option.type === "timeSlot" && option.type === "timeSlot" &&
event.start &&
option.start === formatDateWithoutTz(event.start) && option.start === formatDateWithoutTz(event.start) &&
event.end && event.end &&
option.end === formatDateWithoutTz(event.end) option.end === formatDateWithoutTz(event.end)

View file

@ -1,7 +1,7 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "@rallly/ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "@rallly/ui/tooltip";
import Linkify from "linkify-react";
import Link from "next/link"; import Link from "next/link";
import * as React from "react"; import * as React from "react";
import ReactLinkify from "react-linkify";
export const truncateLink = (href: string, text: string, key: number) => { export const truncateLink = (href: string, text: string, key: number) => {
const textWithoutProtocol = text.replace(/^https?:\/\//i, ""); const textWithoutProtocol = text.replace(/^https?:\/\//i, "");
@ -45,11 +45,21 @@ export const truncateLink = (href: string, text: string, key: number) => {
} }
}; };
const TruncatedLinkify: React.FunctionComponent<{ const TruncatedLinkify = ({ children }: { children: React.ReactNode }) => {
children?: React.ReactNode;
}> = ({ children }) => {
return ( return (
<ReactLinkify componentDecorator={truncateLink}>{children}</ReactLinkify> <Linkify
options={{
render: ({ attributes, content }) => {
return truncateLink(
attributes.href,
content,
attributes.key as number,
);
},
}}
>
{children}
</Linkify>
); );
}; };

View file

@ -43,6 +43,8 @@
"zod": "^3.22.3" "zod": "^3.22.3"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"dotenv-cli": "^7.1.0", "dotenv-cli": "^7.1.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^3.2.4", "prettier": "^3.2.4",

View file

@ -10,20 +10,19 @@
"main": "./src/index.ts", "main": "./src/index.ts",
"types": "./src/index.ts", "types": "./src/index.ts",
"dependencies": { "dependencies": {
"@aws-sdk/client-ses": "^3.292.0", "@aws-sdk/client-ses": "^3.501.0",
"@aws-sdk/credential-provider-node": "^3.292.0", "@aws-sdk/credential-provider-node": "^3.501.0",
"@react-email/components": "0.0.2", "@react-email/components": "^0.0.14",
"@react-email/render": "0.0.6", "@react-email/render": "^0.0.12",
"clsx": "^1.2.1", "nodemailer": "^6.9.8",
"nodemailer": "^6.9.1",
"preview-email": "^3.0.19", "preview-email": "^3.0.19",
"react-email": "^1.9.1" "react-email": "^2.0.0"
}, },
"devDependencies": { "devDependencies": {
"@rallly/tailwind-config": "*", "@rallly/tailwind-config": "*",
"@rallly/tsconfig": "*", "@rallly/tsconfig": "*",
"@rallly/utils": "*", "@rallly/utils": "*",
"@types/nodemailer": "^6.4.7", "@types/nodemailer": "^6.4.14",
"@types/preview-email": "^3.0.1" "@types/preview-email": "^3.0.4"
} }
} }

View file

@ -7,7 +7,7 @@ import previewEmail from "preview-email";
import React from "react"; import React from "react";
import * as templates from "./templates"; import * as templates from "./templates";
import { EmailContext } from "./templates/components/email-context"; import { EmailContext } from "./templates/_components/email-context";
type Templates = typeof templates; type Templates = typeof templates;

View file

@ -0,0 +1,11 @@
export type EmailContext = {
logoUrl: string;
baseUrl: string;
domain: string;
};
export const defaultEmailContext = {
logoUrl: "https://rallly.co/logo.png",
baseUrl: "https://rallly.co",
domain: "rallly.co",
};

View file

@ -0,0 +1,90 @@
import {
Body,
Container,
Head,
Html,
Img,
Link,
Preview,
Tailwind,
} from "@react-email/components";
import { EmailContext } from "./email-context";
import { Section, Text } from "./styled-components";
export interface EmailLayoutProps {
preview: string;
recipientName: string;
footNote?: React.ReactNode;
ctx: EmailContext;
}
const sectionStyles = {
marginTop: "16px",
marginBottom: "16px",
};
export const EmailLayout = ({
preview,
recipientName = "Guest",
children,
footNote,
ctx,
}: React.PropsWithChildren<EmailLayoutProps>) => {
const { logoUrl, baseUrl } = ctx;
return (
<Tailwind>
<Html>
<Head />
<Preview>{preview}</Preview>
<Body className="bg-gray-50 py-5">
<Container className="w-full rounded-md border border-solid border-gray-200 bg-white p-4">
<Img src={logoUrl} alt="Rallly" width={128} />
<Section style={sectionStyles}>
<Text>Hi {recipientName},</Text>
{children}
</Section>
{footNote ? (
<Section className="border-t border-solid border-gray-200">
<Text className="text-sm text-gray-500">{footNote}</Text>
</Section>
) : null}
<Section className="my-0 font-sans text-sm text-gray-500">
<Link className="text-gray-500" href={baseUrl}>
Home
</Link>
<span>&nbsp;&bull;&nbsp;</span>
<Link
className="text-gray-500"
href="https://twitter.com/ralllyco"
>
Twitter
</Link>
<span>&nbsp;&bull;&nbsp;</span>
<Link
className="text-gray-500"
href="https://github.com/lukevella/rallly"
>
Github
</Link>
<span>&nbsp;&bull;&nbsp;</span>
<Link
className="text-gray-500"
href="https://www.paypal.com/donate/?hosted_button_id=7QXP2CUBLY88E"
>
Donate
</Link>
<span>&nbsp;&bull;&nbsp;</span>
<Link
className="text-gray-500"
href={`mailto:${process.env["SUPPORT_EMAIL"]}`}
>
Contact
</Link>
</Section>
</Container>
</Body>
</Html>
</Tailwind>
);
};

View file

@ -1,6 +1,5 @@
import { import {
Button as UnstyledButton, Button as UnstyledButton,
ButtonProps,
Heading as UnstyledHeading, Heading as UnstyledHeading,
Link as UnstyledLink, Link as UnstyledLink,
LinkProps, LinkProps,
@ -37,7 +36,7 @@ export const Domain = ({ ctx }: { ctx: EmailContext }) => {
return <Link href={baseUrl}>{domain}</Link>; return <Link href={baseUrl}>{domain}</Link>;
}; };
export const Button = (props: ButtonProps) => { export const Button = (props: React.ComponentProps<typeof UnstyledButton>) => {
return ( return (
<UnstyledButton <UnstyledButton
{...props} {...props}
@ -79,14 +78,9 @@ export const Heading = (
<UnstyledHeading <UnstyledHeading
{...props} {...props}
as={as} as={as}
className="font-sans font-bold text-gray-900"
style={{ style={{
marginTop: "16px",
marginBottom: "8px",
letterSpacing: "-0.75px",
fontFamily: "sans-serif",
fontWeight: "bold",
fontSize: fontSize[as], fontSize: fontSize[as],
color: "#1E293B",
...props.style, ...props.style,
}} }}
/> />

View file

@ -1,5 +0,0 @@
export type EmailContext = {
logoUrl: string;
baseUrl: string;
domain: string;
};

View file

@ -1,104 +0,0 @@
import {
Body,
Container,
Head,
Html,
Img,
Link,
Preview,
} from "@react-email/components";
import { EmailContext } from "./email-context";
import { fontFamily, Section, Text } from "./styled-components";
export interface EmailLayoutProps {
preview: string;
recipientName: string;
footNote?: React.ReactNode;
ctx: EmailContext;
}
const containerStyles = {
maxWidth: "600px",
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",
};
export const EmailLayout = ({
preview,
recipientName = "Guest",
children,
footNote,
ctx,
}: React.PropsWithChildren<EmailLayoutProps>) => {
const { logoUrl, baseUrl } = ctx;
return (
<Html>
<Head />
<Preview>{preview}</Preview>
<Body style={{ backgroundColor: "#F3F4F6", padding: "16px" }}>
<Container style={containerStyles}>
<Img src={logoUrl} alt="Rallly" width={128} />
<Section style={sectionStyles}>
<Text>Hi {recipientName},</Text>
{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="https://www.paypal.com/donate/?hosted_button_id=7QXP2CUBLY88E"
>
Donate
</Link>
<span>&nbsp;&bull;&nbsp;</span>
<Link
style={linkStyles}
href={`mailto:${process.env["SUPPORT_EMAIL"]}`}
>
Contact
</Link>
</Section>
</Container>
</Body>
</Html>
);
};

View file

@ -1,8 +1,8 @@
import { Column, Row, Section } from "@react-email/components"; import { Column, Row, Section } from "@react-email/components";
import { EmailContext } from "./components/email-context"; import { defaultEmailContext, EmailContext } from "./_components/email-context";
import { EmailLayout } from "./components/email-layout"; import { EmailLayout } from "./_components/email-layout";
import { borderColor, Button, Text } from "./components/styled-components"; import { borderColor, Button, Text } from "./_components/styled-components";
export interface FinalizeHostEmailProps { export interface FinalizeHostEmailProps {
date: string; date: string;
@ -25,7 +25,7 @@ export const FinalizeHostEmail = ({
dow = "Fri", dow = "Fri",
date = "Friday, 12th June 2020", date = "Friday, 12th June 2020",
time = "6:00 PM to 11:00 PM BST", time = "6:00 PM to 11:00 PM BST",
ctx, ctx = defaultEmailContext,
}: FinalizeHostEmailProps) => { }: FinalizeHostEmailProps) => {
return ( return (
<EmailLayout ctx={ctx} recipientName={name} preview="Final date booked!"> <EmailLayout ctx={ctx} recipientName={name} preview="Final date booked!">

View file

@ -1,8 +1,8 @@
import { Column, Row, Section } from "@react-email/components"; import { Column, Row, Section } from "@react-email/components";
import { EmailContext } from "./components/email-context"; import { defaultEmailContext, EmailContext } from "./_components/email-context";
import { EmailLayout } from "./components/email-layout"; import { EmailLayout } from "./_components/email-layout";
import { borderColor, Button, Text } from "./components/styled-components"; import { borderColor, Button, Text } from "./_components/styled-components";
export interface FinalizeParticipantEmailProps { export interface FinalizeParticipantEmailProps {
date: string; date: string;
@ -27,7 +27,7 @@ export const FinalizeParticipantEmail = ({
dow = "Fri", dow = "Fri",
date = "Friday, 12th June 2020", date = "Friday, 12th June 2020",
time = "6:00 PM to 11:00 PM BST", time = "6:00 PM to 11:00 PM BST",
ctx, ctx = defaultEmailContext,
}: FinalizeParticipantEmailProps) => { }: FinalizeParticipantEmailProps) => {
return ( return (
<EmailLayout ctx={ctx} recipientName={name} preview="Final date booked!"> <EmailLayout ctx={ctx} recipientName={name} preview="Final date booked!">

View file

@ -1,5 +1,5 @@
import { EmailContext } from "./components/email-context"; import { defaultEmailContext, EmailContext } from "./_components/email-context";
import { EmailLayout } from "./components/email-layout"; import { EmailLayout } from "./_components/email-layout";
import { import {
Button, Button,
Card, Card,
@ -7,7 +7,7 @@ import {
Heading, Heading,
Text, Text,
trackingWide, trackingWide,
} from "./components/styled-components"; } from "./_components/styled-components";
interface LoginEmailProps { interface LoginEmailProps {
name: string; name: string;
@ -20,7 +20,7 @@ export const LoginEmail = ({
name = "Guest", name = "Guest",
code = "123456", code = "123456",
magicLink = "https://rallly.co", magicLink = "https://rallly.co",
ctx, ctx = defaultEmailContext,
}: LoginEmailProps) => { }: LoginEmailProps) => {
return ( return (
<EmailLayout <EmailLayout

View file

@ -1,7 +1,8 @@
import { defaultEmailContext } from "./_components/email-context";
import NotificationEmail, { import NotificationEmail, {
NotificationBaseProps, NotificationBaseProps,
} from "./components/notification-email"; } from "./_components/notification-email";
import { Text } from "./components/styled-components"; import { Text } from "./_components/styled-components";
export interface NewCommentEmailProps extends NotificationBaseProps { export interface NewCommentEmailProps extends NotificationBaseProps {
authorName: string; authorName: string;
@ -13,7 +14,7 @@ export const NewCommentEmail = ({
authorName = "Someone", authorName = "Someone",
pollUrl = "https://rallly.co", pollUrl = "https://rallly.co",
disableNotificationsUrl = "https://rallly.co", disableNotificationsUrl = "https://rallly.co",
ctx, ctx = defaultEmailContext,
}: NewCommentEmailProps) => { }: NewCommentEmailProps) => {
return ( return (
<NotificationEmail <NotificationEmail

View file

@ -1,6 +1,6 @@
import { EmailContext } from "./components/email-context"; import { defaultEmailContext, EmailContext } from "./_components/email-context";
import { EmailLayout } from "./components/email-layout"; import { EmailLayout } from "./_components/email-layout";
import { Button, Domain, Section, Text } from "./components/styled-components"; import { Button, Domain, Section, Text } from "./_components/styled-components";
interface NewParticipantConfirmationEmailProps { interface NewParticipantConfirmationEmailProps {
name: string; name: string;
@ -12,7 +12,7 @@ export const NewParticipantConfirmationEmail = ({
title = "Untitled Poll", title = "Untitled Poll",
name = "John", name = "John",
editSubmissionUrl = "https://rallly.co", editSubmissionUrl = "https://rallly.co",
ctx, ctx = defaultEmailContext,
}: NewParticipantConfirmationEmailProps) => { }: NewParticipantConfirmationEmailProps) => {
const { domain } = ctx; const { domain } = ctx;
return ( return (

View file

@ -1,7 +1,8 @@
import { defaultEmailContext } from "./_components/email-context";
import NotificationEmail, { import NotificationEmail, {
NotificationBaseProps, NotificationBaseProps,
} from "./components/notification-email"; } from "./_components/notification-email";
import { Text } from "./components/styled-components"; import { Text } from "./_components/styled-components";
export interface NewParticipantEmailProps extends NotificationBaseProps { export interface NewParticipantEmailProps extends NotificationBaseProps {
participantName: string; participantName: string;
@ -13,7 +14,7 @@ export const NewParticipantEmail = ({
participantName = "Someone", participantName = "Someone",
pollUrl = "https://rallly.co", pollUrl = "https://rallly.co",
disableNotificationsUrl = "https://rallly.co", disableNotificationsUrl = "https://rallly.co",
ctx, ctx = defaultEmailContext,
}: NewParticipantEmailProps) => { }: NewParticipantEmailProps) => {
return ( return (
<NotificationEmail <NotificationEmail

View file

@ -1,6 +1,6 @@
import { EmailContext } from "./components/email-context"; import { defaultEmailContext, EmailContext } from "./_components/email-context";
import { EmailLayout } from "./components/email-layout"; import { EmailLayout } from "./_components/email-layout";
import { Button, Card, Link, Text } from "./components/styled-components"; import { Button, Card, Link, Text } from "./_components/styled-components";
export interface NewPollEmailProps { export interface NewPollEmailProps {
title: string; title: string;
@ -38,7 +38,7 @@ export const NewPollEmail = ({
name = "John", name = "John",
adminLink = "https://rallly.co/admin/abcdefg123", adminLink = "https://rallly.co/admin/abcdefg123",
participantLink = "https://rallly.co/invite/wxyz9876", participantLink = "https://rallly.co/invite/wxyz9876",
ctx, ctx = defaultEmailContext,
}: NewPollEmailProps) => { }: NewPollEmailProps) => {
const { baseUrl, domain } = ctx; const { baseUrl, domain } = ctx;
return ( return (

View file

@ -1,11 +1,11 @@
import { EmailContext } from "./components/email-context"; import { defaultEmailContext, EmailContext } from "./_components/email-context";
import { EmailLayout } from "./components/email-layout"; import { EmailLayout } from "./_components/email-layout";
import { import {
Domain, Domain,
Heading, Heading,
Text, Text,
trackingWide, trackingWide,
} from "./components/styled-components"; } from "./_components/styled-components";
interface RegisterEmailProps { interface RegisterEmailProps {
name: string; name: string;
@ -16,7 +16,7 @@ interface RegisterEmailProps {
export const RegisterEmail = ({ export const RegisterEmail = ({
name = "John", name = "John",
code = "123456", code = "123456",
ctx, ctx = defaultEmailContext,
}: RegisterEmailProps) => { }: RegisterEmailProps) => {
return ( return (
<EmailLayout <EmailLayout

View file

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

3955
yarn.lock

File diff suppressed because it is too large Load diff