Skip to content

Commit

Permalink
Merge pull request #60 from opportunity-hack/dev
Browse files Browse the repository at this point in the history
Fix for #59 - Added ics calendar support within email
  • Loading branch information
gregv authored Dec 3, 2023
2 parents 47e107c + 9fc0178 commit fb2d8b9
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 67 deletions.
3 changes: 2 additions & 1 deletion app/data.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Prisma } from '@prisma/client'

export const siteName = 'The Barn Volunteer Portal'
export const siteEmailAddress = '[email protected]'
export const siteName = 'The Barn: Volunteer Portal'
export const siteEmailAddressWithName = siteName + ' <[email protected]>'

export const volunteerTypes = [
{
Expand Down
6 changes: 5 additions & 1 deletion app/routes/_auth+/signup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,11 @@ export async function action({ request }: DataFunctionArgs) {
if (response.status === 'success') {
return redirect(redirectTo.pathname + redirectTo.search)
} else {
submission.error[''] = response.error.message
submission.error[''] = 'There was an error sending the email.' + response.error;
if ( response.error?.message ){
submission.error[''] += ' ' + response.error.message;
}

return json(
{
status: 'error',
Expand Down
31 changes: 21 additions & 10 deletions app/routes/resources+/event-register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export async function action({ request }: DataFunctionArgs) {
}

const invite = generateInvite(event)

if (invite === "") {
console.error(
'There was an error generating an invite for the following event:',
Expand All @@ -123,6 +124,7 @@ export async function action({ request }: DataFunctionArgs) {
)
}


sendEmail({
to: user.email,
subject: `Event Registration Notification`,
Expand Down Expand Up @@ -164,23 +166,32 @@ function generateInvite(event: Event) {

const duration = differenceInMinutes(event.end, event.start)

const endDate = new Date(event.start.getTime() + duration * 60000);

let icalEvent = {
start: [year, month, day, hour, minute] as DateArray,
duration: { minutes: duration },
end: [endDate.getFullYear(), endDate.getMonth() + 1, endDate.getDate(), endDate.getHours(), endDate.getMinutes()] as DateArray,
// duration: { minutes: duration }, // Not sure this is used
title: event.title,
organizer: { name: 'Volunteer Coordinator', email: siteEmailAddress },
};

let invite: string = "";
createEvent(icalEvent, (error, value) => {
if (error) {
console.log(error);
return;
}
invite = value;
})
const invite = createEvent(icalEvent)
if (invite.error) {
console.error((invite.error));
return "";
}
// Log the event as a string
console.log("Calendar invite: " + invite.value);

// Check if invite.value is a string
if (typeof invite.value === 'string') {
// Convert to a buffer
const inviteBuffer = Buffer.from(invite.value);
return inviteBuffer
}

return invite
return "";
}

async function notifyAdmins({
Expand Down
86 changes: 36 additions & 50 deletions app/utils/email.server.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,9 @@
import { type ReactElement } from 'react'
import { renderAsync } from '@react-email/components'
import { z } from 'zod'
import { siteEmailAddress } from '~/data.ts'
import { siteEmailAddressWithName } from '~/data.ts'
import { Resend } from 'resend';

const resendErrorSchema = z.union([
z.object({
name: z.string(),
message: z.string(),
statusCode: z.number(),
}),
z.object({
name: z.literal('UnknownError'),
message: z.literal('Unknown Error'),
statusCode: z.literal(500),
cause: z.any(),
}),
])
type ResendError = z.infer<typeof resendErrorSchema>

const resendSuccessSchema = z.object({
id: z.string(),
})
const resend = new Resend(process.env.RESEND_API_KEY);

export async function sendEmail({
react,
Expand All @@ -34,10 +17,10 @@ export async function sendEmail({
| { react: ReactElement; html?: never; text?: never }
)) {
// TODO: find out what email address to use here
const from = siteEmailAddress
const from = siteEmailAddressWithName

const email = {
from,
from,
...options,
...(react ? await renderReactEmail(react) : null),
}
Expand All @@ -55,40 +38,43 @@ export async function sendEmail({
} as const
}

const response = await fetch('https://api.resend.com/emails', {
method: 'POST',
body: JSON.stringify(email),
headers: {
Authorization: `Bearer ${process.env.RESEND_API_KEY}`,
'Content-Type': 'application/json',
},
})
const data = await response.json()
const parsedData = resendSuccessSchema.safeParse(data)
// Make HTML an empty string if it's not provided
if (!email.html) {
email.html = ''
}
// Make text an empty string if it's not provided
if (!email.text) {
email.text = ''
}
// Log the email contents
console.info('🔶 sending email:', JSON.stringify(email))

try {
const response = await resend.emails.send({
from: email.from,
to: email.to,
subject: email.subject,
html: email.html,
text: email.text,
attachments: email.attachments,
});

if (response.ok && parsedData.success) {
return {
status: 'success',
data: parsedData,
data: response,
} as const
} else {
const parseResult = resendErrorSchema.safeParse(data)
if (parseResult.success) {
return {
status: 'error',
error: parseResult.data,
} as const
} else {
return {

// Catch full exception
} catch (e : any) {
console.error('🔴 error sending email:', JSON.stringify(email))
return {
status: 'error',
error: {
name: 'UnknownError',
message: 'Unknown Error',
statusCode: 500,
cause: data,
} satisfies ResendError,
} as const
}
message: e.message || 'Unknown error',
code: e.code || 'Unknown code',
response: e.response || 'Unknown response',
}
}
}
}

Expand Down
177 changes: 177 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit fb2d8b9

Please sign in to comment.