User Created Email

This commit is contained in:
Atridad Lahiji 2023-08-22 21:26:26 -06:00 committed by atridadl
parent 8a840152e4
commit bd8231912c
No known key found for this signature in database
5 changed files with 72 additions and 93 deletions

View file

@ -1,52 +0,0 @@
import {
Body,
Container,
Head,
Heading,
Hr,
Html,
Img,
Preview,
Section,
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
interface GoodbyeTemplateProps {
name: string;
}
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: "http://localhost:3000";
export const Goodbye = ({ name }: GoodbyeTemplateProps) => (
<Html>
<Head />
<Preview>Sorry to see you go... 😭</Preview>
<Tailwind>
<Body className="bg-white my-auto mx-auto font-sans">
<Container className="border border-solid border-[#eaeaea] rounded my-[40px] mx-auto p-[20px] w-[465px]">
<Section className="mt-[32px]">
<Img
src={`${baseUrl}/logo.webp`}
width="40"
height="37"
alt={`Sprint Padawan Logo`}
className="my-0 mx-auto"
/>
</Section>
<Heading className="text-4xl">Farewell, {name}...</Heading>
<Text>{"Were sorry to see you go."}</Text>
<Text>
Your data has been deleted, including all room history, user data,
votes, etc.
</Text>
<Hr className="border border-solid border-[#eaeaea] my-[26px] mx-0 w-full" />
<Text> The Sprint Padawan team</Text>
</Container>
</Body>
</Tailwind>
</Html>
);

View file

@ -8,7 +8,7 @@ import {
Img, Img,
Preview, Preview,
Section, Section,
Tailwind, // Tailwind,
Text, Text,
} from "@react-email/components"; } from "@react-email/components";
import * as React from "react"; import * as React from "react";
@ -25,33 +25,33 @@ export const Welcome = ({ name }: WelcomeTemplateProps) => (
<Html> <Html>
<Head /> <Head />
<Preview>🎉 Welcome to Sprint Padawan! 🎉</Preview> <Preview>🎉 Welcome to Sprint Padawan! 🎉</Preview>
<Tailwind> {/* <Tailwind> */}
<Body className="bg-white my-auto mx-auto font-sans"> <Body className="bg-white my-auto mx-auto font-sans">
<Container className="border border-solid border-[#eaeaea] rounded my-[40px] mx-auto p-[20px] w-[465px]"> <Container className="border border-solid border-[#eaeaea] rounded my-[40px] mx-auto p-[20px] w-[465px]">
<Section className="mt-[32px]"> <Section className="mt-[32px]">
<Img <Img
src={`${baseUrl}/logo.webp`} src={`${baseUrl}/logo.webp`}
width="40" width="40"
height="37" height="37"
alt={`Sprint Padawan Logo`} alt={`Sprint Padawan Logo`}
className="my-0 mx-auto" className="my-0 mx-auto"
/> />
</Section> </Section>
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0"> <Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
🎉 Welcome to Sprint Padawan, <strong>{name}</strong>! 🎉 🎉 Welcome to Sprint Padawan, <strong>{name}</strong>! 🎉
</Heading> </Heading>
<Text className="text-black text-[14px] leading-[24px]"> <Text className="text-black text-[14px] leading-[24px]">
Hello {name}, Hello {name},
</Text> </Text>
<Text>Thank you for signing up for Sprint Padawan!</Text> <Text>Thank you for signing up for Sprint Padawan!</Text>
<Text> <Text>
If at any point you encounter issues, please let me know at If at any point you encounter issues, please let me know at
support@sprintpadawan.dev. support@sprintpadawan.dev.
</Text> </Text>
<Hr className="border border-solid border-[#eaeaea] my-[26px] mx-0 w-full" /> <Hr className="border border-solid border-[#eaeaea] my-[26px] mx-0 w-full" />
<Text> The Sprint Padawan team</Text> <Text> The Sprint Padawan team</Text>
</Container> </Container>
</Body> </Body>
</Tailwind> {/* </Tailwind> */}
</Html> </Html>
); );

View file

@ -3,7 +3,11 @@ import {
onUserCreatedHandler, onUserCreatedHandler,
onUserDeletedHandler, onUserDeletedHandler,
} from "~/server/webhookHelpers"; } from "~/server/webhookHelpers";
import { WebhookEventBodySchema, WebhookEvents } from "~/utils/types"; import {
WebhookEventBody,
WebhookEventBodySchema,
WebhookEvents,
} from "~/utils/types";
export const config = { export const config = {
runtime: "edge", runtime: "edge",
@ -12,12 +16,17 @@ export const config = {
export default async function handler(req: NextRequest) { export default async function handler(req: NextRequest) {
try { try {
const requestBody = WebhookEventBodySchema.parse(await req.json()); const eventBody = (await req.json()) as WebhookEventBody;
const { data, type } = WebhookEventBodySchema.parse(eventBody);
let success = false; let success = false;
switch (requestBody.type) { switch (type) {
case WebhookEvents.USER_CREATED: case WebhookEvents.USER_CREATED:
success = await onUserCreatedHandler(requestBody.data.id); success = await onUserCreatedHandler(
data.id,
`${data.first_name} ${data.last_name}`,
data.email_addresses?.map((email) => email.email_address) || []
);
if (success) { if (success) {
return NextResponse.json( return NextResponse.json(
{ result: "USER CREATED" }, { result: "USER CREATED" },
@ -31,7 +40,7 @@ export default async function handler(req: NextRequest) {
} }
case WebhookEvents.USER_DELETED: case WebhookEvents.USER_DELETED:
success = await onUserDeletedHandler(requestBody.data.id); success = await onUserDeletedHandler(data.id);
return NextResponse.json( return NextResponse.json(
{ result: "USER DELETED" }, { result: "USER DELETED" },
@ -44,7 +53,8 @@ export default async function handler(req: NextRequest) {
{ status: 400, statusText: "INVALID WEBHOOK EVENT TYPE" } { status: 400, statusText: "INVALID WEBHOOK EVENT TYPE" }
); );
} }
} catch { } catch (error) {
console.log(error);
return NextResponse.json( return NextResponse.json(
{ result: "INVALID WEBHOOK EVENT BODY" }, { result: "INVALID WEBHOOK EVENT BODY" },
{ status: 400, statusText: "INVALID WEBHOOK EVENT BODY" } { status: 400, statusText: "INVALID WEBHOOK EVENT BODY" }

View file

@ -2,6 +2,10 @@ import { eq } from "drizzle-orm";
import { db } from "./db"; import { db } from "./db";
import { rooms } from "./schema"; import { rooms } from "./schema";
import { env } from "~/env.mjs"; import { env } from "~/env.mjs";
import { Welcome } from "~/components/templates/Welcome";
import { Resend } from "resend";
const resend = new Resend(env.RESEND_API_KEY);
export const onUserDeletedHandler = async (userId: string) => { export const onUserDeletedHandler = async (userId: string) => {
try { try {
@ -13,7 +17,11 @@ export const onUserDeletedHandler = async (userId: string) => {
} }
}; };
export const onUserCreatedHandler = async (userId: string) => { export const onUserCreatedHandler = async (
userId: string,
userName: string,
userEmails: string[]
) => {
const userUpdateResponse = await fetch( const userUpdateResponse = await fetch(
`https://api.clerk.com/v1/users/${userId}/metadata`, `https://api.clerk.com/v1/users/${userId}/metadata`,
{ {
@ -33,5 +41,14 @@ export const onUserCreatedHandler = async (userId: string) => {
} }
); );
userEmails.forEach((userEmail) => {
void resend.sendEmail({
from: "no-reply@sprintpadawan.dev",
to: userEmail,
subject: "🎉 Welcome to Sprint Padawan! 🎉",
react: Welcome({ name: userName }),
});
});
return userUpdateResponse.ok; return userUpdateResponse.ok;
}; };

View file

@ -23,14 +23,18 @@ export const WebhookEventBodySchema = z.object({
.array( .array(
z.object({ z.object({
email_address: z.string().email(), email_address: z.string().email(),
id: z.string(), id: z.string().optional(),
verification: z.object({ verification: z
status: z.string(), .object({
strategy: z.string(), status: z.string().optional(),
}), strategy: z.string().optional(),
})
.optional(),
}) })
) )
.optional(), .optional(),
first_name: z.string().optional(),
last_name: z.string().optional(),
}), }),
type: z.string(), type: z.string(),
}); });