Skip to content

Commit

Permalink
Merge pull request #1 from drewlyton/react-email
Browse files Browse the repository at this point in the history
React email
  • Loading branch information
drewlyton authored Aug 19, 2023
2 parents 6878f8f + 5eca51f commit 83d6617
Show file tree
Hide file tree
Showing 44 changed files with 5,696 additions and 1,291 deletions.
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
public
build
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ node_modules

# Local Netlify folder
.netlify

.react-email

# Contentlayer
.contentlayer
2 changes: 1 addition & 1 deletion app/animations/ProductAni.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const ProductAni: React.FC<AniComp> = ({ link }) => {
function animate() {
const timeline = gsap.timeline({ repeat: -1, yoyo: true });
const mySplitText = new SplitText("#software-header", {
type: "words",
type: "words"
});
mySplitText.words[0]?.classList.add("left-bracket");
mySplitText.words[3]?.classList.add("right-bracket");
Expand Down
8 changes: 4 additions & 4 deletions app/components/StoriesSection.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { cloneElement, PropsWithChildren, ReactElement } from "react";

Check warning on line 1 in app/components/StoriesSection.tsx

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

Imports "PropsWithChildren" and "ReactElement" are only used as types
import type IStory from "../data/Story";
import Story from "./Story";
import type Story from "../data/Story";
import StoryCard from "./Story";

interface Props extends PropsWithChildren {
label: string;
id: string;
animation: ReactElement;
stories: IStory[];
stories: Story[];
}

const StoriesSection: React.FC<Props> = ({
Expand All @@ -33,7 +33,7 @@ const StoriesSection: React.FC<Props> = ({
</div>
<div className="flex flex-wrap items-start">
{stories.map((story) => (
<Story story={story} key={story.id} />
<StoryCard story={story} key={story.id} />
))}
</div>
</section>
Expand Down
8 changes: 4 additions & 4 deletions app/components/Story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import classNames from "classnames";
import React from "react";
import routes from "../helpers/routes";
import { truncateString } from "../helpers/truncateString";
import type IStory from "../data/Story";
import type Story from "../data/Story";

const Story: React.FC<Props> = ({ story }) => {
const StoryCard: React.FC<Props> = ({ story }) => {
return (
<div className="post-item relative px-2 py-4 flex-grow">
<Link to={routes.story(story.slug)}>
Expand Down Expand Up @@ -40,7 +40,7 @@ const Story: React.FC<Props> = ({ story }) => {
};

type Props = {
story: IStory;
story: Story;
};

export default Story;
export default StoryCard;
74 changes: 46 additions & 28 deletions app/components/Subscribe.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,65 @@
import { useFetcher } from "@remix-run/react";
import React from "react";

export const Subscribe: React.FC = () => {
export const Subscribe: React.FC<{
refLink?: string;
noPreamble?: boolean;
}> = ({ refLink, noPreamble }) => {
const { Form, submission, data } = useFetcher();

const formState = submission
? "submitting"
: data?.subscription
: data?.subscribed
? "success"
: data?.error
? "error"
: "idle";

console.log(data);

return (
<div className="bg-black bg-opacity-5 dark:bg-opacity-20 px-10 py-8 rounded-2xl mb-8">
<h2 className="mb-2 text-3xl">LET'S KEEP THE CONVERSATION GOING</h2>
{/* <p>
I write these essays for myself. However, I hope that by sharing the
things I'm learning with others, we all can become a better
entrepreneurs, communicators and internet citizens.
</p> */}
<p>
If this article has been valuable and you'd like to be notified the next
time I post, you can subscribe to my newsletter,{" "}
<a
target={"_blank"}
rel="noreferrer"
className="italic font-bold wave-border bottom dotted"
href="https://www.getrevue.co/profile/realizingthought"
>
Realizing Thought
</a>
.
</p>
<p>
Each week, I share my latest post along with three other works from
people smarter than me on whatever topic is top of mind. Of course, you
can unsubscribe at anytime.
</p>
{noPreamble ? (
<>
<h2 className="mb-2 text-3xl uppercase">
Subscribe to my newsletter
</h2>
<p>
Each week, I share my latest post along with three other works from
people smarter than me on whatever topic is top of mind. Of course,
you can unsubscribe at anytime.
</p>

<hr className="opacity-20 border-black dark:border-white my-6 w-3/4 mx-auto" />
</>
) : (
<>
<h2 className="mb-2 text-3xl">LET'S KEEP THE CONVERSATION GOING</h2>
<p>
If this article has been valuable and you'd like to be notified the
next time I post, you can subscribe to my newsletter,{" "}
<a
target={"_blank"}
rel="noreferrer"
className="italic font-bold wave-border bottom dotted"
href="https://www.getrevue.co/profile/realizingthought"
>
Realizing Thought
</a>
.
</p>
<p>
Each week, I share my latest post along with three other works from
people smarter than me on whatever topic is top of mind. Of course,
you can unsubscribe at anytime.
</p>

<hr className="opacity-20 border-black dark:border-white my-6 w-3/4 mx-auto" />
<hr className="opacity-20 border-black dark:border-white my-6 w-3/4 mx-auto" />
</>
)}

<Form
action="/newsletter"
action="/newsletter/subscribe"
method="post"
className={formState === "success" ? "hidden" : ""}
>
Expand All @@ -53,6 +70,7 @@ export const Subscribe: React.FC = () => {
placeholder="Your email..."
className="px-4 py-3 rounded-md flex-1 mr-2"
/>
<input hidden aria-hidden readOnly value={refLink} />
<button type="submit">
{formState === "submitting" ? "Subscribing..." : "Subscribe"}
</button>
Expand Down
32 changes: 32 additions & 0 deletions app/data/GetNewsletter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { gql } from "graphql-request";

export default gql`
query Newsletters($issueNumber: Int) {
newsletter(where: { issueNumber: $issueNumber }) {
createdAt
publishedAt
id
issueNumber
messageBody
preview
subject
publishedAt
updatedAt
story {
title
description
featuredImage {
url
}
slug
author {
name
bio
picture {
url
}
}
}
}
}
`;
32 changes: 32 additions & 0 deletions app/data/GetNewsletters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { gql } from "graphql-request";

export default gql`
query Newsletters {
newsletters(orderBy: publishedAt_DESC) {
createdAt
publishedAt
id
issueNumber
messageBody
preview
subject
publishedAt
updatedAt
story {
title
description
featuredImage {
url
}
slug
author {
name
bio
picture {
url
}
}
}
}
}
`;
13 changes: 13 additions & 0 deletions app/data/Newsletter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type Story from "./Story";

export type Newsletter = {
createdAt: string;
publishedAt: string;
id: string;
issueNumber: number;
messageBody: string;
preview: boolean;
subject: string;
updatedAt: string;
story: Story;
};
File renamed without changes.
2 changes: 1 addition & 1 deletion app/data/Story.d.ts → app/data/Story.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { RichText } from "./RichText";

export default interface IStory {
export default interface Story {
id: string;
title: string;
description: string;
Expand Down
6 changes: 6 additions & 0 deletions app/data/sendgrid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import client from "@sendgrid/client";
import mail from "@sendgrid/mail";
client.setApiKey(process.env.SENDGRID_API_KEY);
mail.setApiKey(process.env.SENDGRID_API_KEY);

export { client as sendgridClient, mail as sendgridMailer };
47 changes: 47 additions & 0 deletions app/emails/ConfirmSubscription.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Column, Img, Link, Row, Section, Text } from "@react-email/components";

export function ConfirmSubscription({ recipient }: { recipient: string }) {
return (
<Section>
<Row>
<Text className="text-2xl font-serif font-semibold">
Hey friend 👋,
</Text>
<Text className="text-base">
Thanks for subscribing to my newsletter!
</Text>
<Text className="text-base">
Click the link below to confirm your subscription and recieve a
notification whenever I post something new:
</Text>
<Link
href={`https://drewis.cool/newsletter/confirm?email=${recipient}`}
className="text-xl underline"
>
Confirm Subscription
</Link>
</Row>
<Text className="text-sm font-semibold mt-6 ">Best,</Text>
<Row className="mb-8">
<Column className="w-[40px]" valign="top">
<Img
src={"https://media.graphassets.com/DaEP4yDeQkuwmEl0hfrf"}
width={"100%"}
/>
</Column>
<Column>
<Text className="text-base ml-4 my-0">Drew Lyton</Text>
</Column>
</Row>
<Row>
<Text className="mt-8 italic text-gray-500 mb-1 font-bold">
Received this by mistake?
</Text>
<Text className="text-gray-500 m-0">
Feel free to disregard this email - I won't send you anything unless
you click the link above. Have a wonderful day 👍
</Text>
</Row>
</Section>
);
}
47 changes: 47 additions & 0 deletions app/emails/EmailFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
Button,
Column,
Hr,
Img,
Link,
Section,
Text
} from "@react-email/components";

export function EmailFooter({ recipient }: { recipient: string }) {
const baseUrl =
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: "https://www.drewis.cool";

return (
<>
<Hr className="my-7" />
<Section className="mb-5">
<Column align="left">
<Link href="https://www.drewis.cool/?ref=newsletter" target="_blank">
<Img
src={"https://www.drewis.cool/static/logo-text.png"}
width="96"
alt="Drew's logo - a handrawn symbol of the letter D."
/>
</Link>
</Column>
<Column align="right">
<Button
className="text-gray-500 text-xs "
href={`${baseUrl}/newsletter/unsubscribe?recipient=${recipient}`}
>
Unsubscribe
</Button>
</Column>
</Section>
<Section>
<Text className="text-[8px] text-center text-bgApache">
<span className="block">998 Salisbury Square</span>
<span className="block">Charlottesville, VA 22901</span>
</Text>
</Section>
</>
);
}
23 changes: 23 additions & 0 deletions app/emails/EmailLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Body, Container, Html, Section } from "@react-email/components";
import { EmailFooter } from "./EmailFooter";
import { TailwindEmailConfig } from "./TailwindEmailConfig";
import type { PropsWithChildren } from "react";

export const EmailLayout: React.FC<
PropsWithChildren<{ recipient: string }>
> = ({ children, recipient }) => {
return (
<TailwindEmailConfig>
<Html>
<Body className="font-sans bg-bgApache">
<Section>
<Container>
{children}
<EmailFooter recipient={recipient} />
</Container>
</Section>
</Body>
</Html>
</TailwindEmailConfig>
);
};
Loading

0 comments on commit 83d6617

Please sign in to comment.