Skip to content

Commit

Permalink
feat: send email after unregistration (#73)
Browse files Browse the repository at this point in the history
Co-authored-by: Anton Lilleby <[email protected]>
  • Loading branch information
an2n and Anton Lilleby authored Jun 24, 2024
1 parent 740017d commit b88f89f
Show file tree
Hide file tree
Showing 18 changed files with 389 additions and 92 deletions.
5 changes: 0 additions & 5 deletions app/src/components/external/EventFormExternal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,6 @@
{#if $unregistrationMessage?.text}
<Alert class="mt-20" color={getAlertColor($unregistrationMessage)}>
{$unregistrationMessage.text}
{#if $unregistrationMessage.token}
<div class="w-[400px] break-words border border-gray-300 p-2 text-xs">
{`/event/unregistration/${$unregistrationMessage.token}`}
</div>
{/if}
</Alert>
{:else}
<UnregistrationFormExternal
Expand Down
11 changes: 8 additions & 3 deletions app/src/components/shared/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,21 @@
</script>

<header class="flex h-[100px] w-full items-center justify-between px-4 pt-2 lg:px-20">
<a class={isRoot ? "pointer-events-none" : "pointer-events-auto"} href="/">
<a
class={isRoot
? "pointer-events-none flex items-center"
: "pointer-events-auto flex items-center"}
href="/"
>
<img
fetchpriority="high"
class="block h-12 select-none sm:h-14 dark:hidden"
class="absolute h-12 select-none opacity-100 sm:h-14 dark:opacity-5"
alt="Animert Capra, Fryde og Liflig-logo"
src={hasPerformanceIssue ? logoSm : logo}
/>
<img
fetchpriority="high"
class="hidden h-12 select-none sm:h-14 dark:block"
class="absolute h-12 select-none opacity-5 sm:h-14 dark:opacity-100"
alt="Animert Capra, Fryde og Liflig-logo"
src={hasPerformanceIssue ? logoSmDark : logoDark}
/>
Expand Down
60 changes: 46 additions & 14 deletions app/src/lib/actions/external/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
registrationSchemaExternal,
unregistrationSchemaExternal,
} from "$lib/schemas/external/schema";
import { sendEventRegistrationConfirmed } from "$lib/email/event-registration";
import { sendRegistrationConfirmed } from "$lib/email/event/registration";
import { sendConfirmUnregistration } from "$lib/email/event/unregistration";

export const submitRegistrationExternal: Actions["submitRegistrationExternal"] = async ({
request,
Expand Down Expand Up @@ -55,7 +56,7 @@ export const submitRegistrationExternal: Actions["submitRegistrationExternal"] =
});
}

const eventContent = await getEventContent({ id });
const eventContent = await getEventContent({ document_id: id });

if (!eventContent) {
console.error("Error: The specified event does not exist as content");
Expand Down Expand Up @@ -149,15 +150,17 @@ export const submitRegistrationExternal: Actions["submitRegistrationExternal"] =
organiser: eventContent.organisers.join(" | "),
};

const { error: emailError } = await sendEventRegistrationConfirmed(emailPayload);
if (process.env.NODE_ENV !== "development") {
const { error: emailError } = await sendRegistrationConfirmed(emailPayload);

if (emailError) {
console.error("Error: Failed to send email");
if (emailError) {
console.error("Error: Failed to send email");

return message(registrationForm, {
text: "Det har oppstått en feil. Du har blitt påmeldt arrangement, men e-post bekreftelse er ikke sendt.",
warning: true,
});
return message(registrationForm, {
text: "Det har oppstått en feil. Du har blitt påmeldt arrangement, men e-post bekreftelse er ikke sendt.",
warning: true,
});
}
}

return message(registrationForm, {
Expand Down Expand Up @@ -209,9 +212,7 @@ export const submitUnregistrationExternal: Actions["submitUnregistrationExternal
data: { email },
} = unregistrationForm;

const data = { event_id, email };

const eventParticipant = await getEventParticipant(data);
const eventParticipant = await getEventParticipant({ event_id, email });

if (!eventParticipant.data?.email || !eventParticipant.data?.attending) {
return message(unregistrationForm, {
Expand All @@ -220,11 +221,42 @@ export const submitUnregistrationExternal: Actions["submitUnregistrationExternal
});
}

const eventContent = await getEventContent({ document_id: id });

if (!eventContent) {
console.error("Error: The specified event does not exist as content");

return message(unregistrationForm, {
text: "Det har oppstått et problem. Du kan ikke melde deg av dette arrangementet.",
error: true,
});
}

const data = { document_id: id, event_id, email };
const secret = getUnsubscribeSecret(data);
const token = jwt.sign({ data }, secret, { expiresIn: "2h" });

// send jwt token to email
// returning for demo purpose
const emailPayload = {
id,
mailTo: email,
summary: eventContent.title,
organiser: eventContent.organisers.join(" | "),
token,
};

if (process.env.NODE_ENV !== "development") {
const { error: emailError } = await sendConfirmUnregistration(emailPayload);

if (emailError) {
console.error("Error: Failed to send email");

return message(unregistrationForm, {
text: "Det har oppstått et problem. Du kan ikke melde deg av dette arrangementet.",
warning: true,
});
}
}

return message(unregistrationForm, {
token,
text: "En e-post har blitt sendt til adressen du oppga. Vennligst følg instruksjonen i e-posten for å fullføre.",
Expand Down
59 changes: 47 additions & 12 deletions app/src/lib/actions/internal/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getEventContent } from "$lib/server/sanity/queries";
import {
getEvent,
getEventParticipant,
updateEventParticipantAttending,
setParticipantNotAttending,
} from "$lib/server/supabase/queries";
import {
deleteEventParticipant,
Expand All @@ -20,7 +20,8 @@ import {
registrationSchemaInternal,
unregistrationSchemaInternal,
} from "$lib/schemas/internal/schema";
import { sendEventRegistrationConfirmed } from "$lib/email/event-registration";
import { sendRegistrationConfirmed } from "$lib/email/event/registration";
import { sendUnregistrationConfirmed } from "$lib/email/event/unregistration";

export const submitRegistrationInternal: Actions["submitRegistrationInternal"] = async ({
request,
Expand Down Expand Up @@ -69,7 +70,7 @@ export const submitRegistrationInternal: Actions["submitRegistrationInternal"] =
});
}

const eventContent = await getEventContent({ id });
const eventContent = await getEventContent({ document_id: id });

if (!eventContent) {
console.error("Error: The specified event does not exist as content");
Expand Down Expand Up @@ -166,7 +167,7 @@ export const submitRegistrationInternal: Actions["submitRegistrationInternal"] =
};

if (process.env.NODE_ENV !== "development") {
const { error: emailError } = await sendEventRegistrationConfirmed(emailPayload);
const { error: emailError } = await sendRegistrationConfirmed(emailPayload);

if (emailError) {
console.error("Error: Failed to send email");
Expand Down Expand Up @@ -234,24 +235,58 @@ export const submitUnregistrationInternal: Actions["submitUnregistrationInternal

const eventParticipant = await getEventParticipant(data);

if (eventParticipant.data?.attending) {
await updateEventParticipantAttending(data);

if (!eventParticipant.data?.attending) {
return message(unregistrationForm, {
success: true,
text: "Du er nå meldt av arrangementet.",
text: "Du er allerede meldt av arrangementet. Takk for interessen din!",
warning: true,
});
}

if (!eventParticipant.data?.email) {
return message(unregistrationForm, {
warning: true,
text: "Vi finner dessverre ingen opplysninger om din påmelding til arrangementet.",
error: true,
});
}

const attendingResult = await setParticipantNotAttending(data);
if (!attendingResult) {
console.error("Error: Failed to update participant attending");

return message(unregistrationForm, {
text: "Det har oppstått en feil. Du kan ikke melde deg av dette arrangementet.",
error: true,
});
}

const eventContent = await getEventContent({ document_id: id });

const emailPayload = {
id,
mailTo: email,
summary: eventContent.title,
description: eventContent.summary,
start: eventContent.start,
end: eventContent.end,
location: eventContent.place,
organiser: eventContent.organisers.join(" | "),
};

if (process.env.NODE_ENV !== "development") {
const { error: emailError } = await sendUnregistrationConfirmed(emailPayload);

if (emailError) {
console.error("Error: Failed to send email");

return message(unregistrationForm, {
text: "Det har oppstått en feil. Du er meldt av arrangement 👋 men e-post bekreftelse er ikke sendt.",
warning: true,
});
}
}

return message(unregistrationForm, {
warning: true,
text: "Du er allerede meldt av arrangementet. Takk for interessen din!",
message: "Du er nå meldt av arrangementet 👋 Vi har sendt deg en bekreftelse på e-post.",
success: true,
});
};
5 changes: 3 additions & 2 deletions app/src/lib/auth/secret.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { APP_SECRET } from "$env/static/private";
import type { TokenData } from "$models/jwt.model";

export const getUnsubscribeSecret = ({ event_id, email }: { event_id: number; email: string }) => {
const secret = `${event_id}-${email}-${APP_SECRET}`;
export const getUnsubscribeSecret = ({ document_id, event_id, email }: TokenData) => {
const secret = `${document_id}-${event_id}-${email}-${APP_SECRET}`;
return secret;
};
80 changes: 80 additions & 0 deletions app/src/lib/email/event/canceled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { PUBLIC_APP_BASE_URL } from "$env/static/public";
import ical, { ICalAttendeeRole, ICalAttendeeStatus, ICalCalendarMethod } from "ical-generator";
import { sendMail } from "$lib/email/nodemailer";

interface EventProps {
id: string;
mailTo: string;
summary: string;
description?: string;
start: string;
end: string;
location: string;
organiser: string;
}

interface EmailParams extends Pick<EventProps, "mailTo" | "organiser" | "summary"> {
subject: string;
icsFile: Buffer;
}

export const sendCanceled = async (props: EventProps) => {
const icsFile = createIcsFile(props);
const mailParams = createMailParams({
...props,
subject: `Avlyst arrangement: ${props.summary}`,
icsFile,
});

const result = await sendMail(mailParams);
return result;
};

const createIcsFile = ({
id,
summary,
description,
start,
end,
location,
organiser,
mailTo,
}: EventProps) => {
const url = `${PUBLIC_APP_BASE_URL}/event/${id}`;

const calendar = ical({ name: organiser, method: ICalCalendarMethod.CANCEL });
calendar.createEvent({
id,
summary,
description,
location,
start,
end,
url,
attendees: [
{
email: mailTo,
status: ICalAttendeeStatus.ACCEPTED,
role: ICalAttendeeRole.REQ,
},
],
organizer: {
name: organiser,
email: "[email protected]",
},
});

return Buffer.from(calendar.toString());
};

const createMailParams = ({ organiser, mailTo, subject, icsFile }: EmailParams) => {
return {
from: `${organiser} <[email protected]>`,
to: mailTo,
subject,
icalEvent: {
method: "request",
content: icsFile,
},
};
};
Loading

0 comments on commit b88f89f

Please sign in to comment.