Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bookings emails #680

Merged
merged 15 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions app/emails/bookings-updates-template.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import {
Button,
Html,
Text,
Link,
Head,
render,
Container,
Heading,
} from "@react-email/components";
import type { ClientHint } from "~/modules/booking/types";
import { getDateTimeFormatFromHints } from "~/utils/client-hints";
import { SERVER_URL } from "~/utils/env";
import { LogoForEmail } from "./logo";
import { styles } from "./styles";
import type { BookingForEmail } from "./types";

interface Props {
heading: string;
booking: BookingForEmail;
assetCount: number;
hints: ClientHint;
hideViewButton?: boolean;
}

export function BookingUpdatesEmailTemplate({
booking,
heading,
hints,
assetCount,
hideViewButton = false,
}: Props) {
const fromDate = getDateTimeFormatFromHints(hints, {
dateStyle: "short",
timeStyle: "short",
}).format(booking.from as Date);
const toDate = getDateTimeFormatFromHints(hints, {
dateStyle: "short",
timeStyle: "short",
}).format(booking.to as Date);
return (
<Html>
<Head>
<title>Bookings update from Shelf.nu</title>
</Head>

<Container
style={{ padding: "32px 16px", textAlign: "center", maxWidth: "100%" }}
>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
marginBottom: "32px",
}}
>
<LogoForEmail />
</div>
<div style={{ margin: "32px" }}>
<Heading as="h1" style={{ ...styles.h1 }}>
{heading}
</Heading>
<Heading as="h2" style={{ ...styles.h2 }}>
{booking.name} | {assetCount}{" "}
{assetCount === 1 ? "asset" : "assets"}
</Heading>
<p style={{ ...styles.p }}>
<span style={{ color: "#101828", fontWeight: "600" }}>
Custodian:
</span>{" "}
{`${booking.custodianUser?.firstName} ${booking.custodianUser?.lastName}` ||
booking.custodianTeamMember?.name}
</p>
<p style={{ ...styles.p }}>
<span style={{ color: "#101828", fontWeight: "600" }}>From:</span>{" "}
{fromDate}
</p>
<p style={{ ...styles.p }}>
<span style={{ color: "#101828", fontWeight: "600" }}>To:</span>{" "}
{toDate}
</p>
</div>

{!hideViewButton && (
<Button
href={`${SERVER_URL}/bookings/${booking.id}`}
style={{
...styles.button,
textAlign: "center",
marginBottom: "32px",
}}
>
View booking in app
</Button>
)}

<Text style={{ fontSize: "14px", color: "#344054" }}>
This email was sent to{" "}
<Link
style={{ color: "#EF6820" }}
href={`mailto:${booking.custodianUser!.email}`}
>
{booking.custodianUser!.email}
</Link>{" "}
because it is part of the workspace{" "}
<span style={{ color: "#101828", fontWeight: "600" }}>
"{booking.organization.name}"
</span>
. <br /> If you think you weren’t supposed to have received this email
please{" "}
<Link
style={{ color: "#344054", textDecoration: "underline" }}
href={`mailto:${booking.organization.owner.email}`}
>
contact the owner
</Link>{" "}
of the workspace.
</Text>
<Text
style={{ marginBottom: "32px", fontSize: "14px", color: "#344054" }}
>
{" "}
© 2024 Shelf.nu
</Text>
</Container>
</Html>
);
}

/*
*The HTML content of an email will be accessed by a server file to send email,
we cannot import a TSX component in a server file so we are exporting TSX converted to HTML string using render function by react-email.
*/
export const bookingUpdatesTemplateString = ({
DonKoko marked this conversation as resolved.
Show resolved Hide resolved
booking,
heading,
assetCount,
hints,
hideViewButton = false,
}: Props) =>
render(
<BookingUpdatesEmailTemplate
booking={booking}
heading={heading}
assetCount={assetCount}
hints={hints}
hideViewButton={hideViewButton}
/>
);
33 changes: 13 additions & 20 deletions app/emails/invite-template.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import {
Button,
Html,
Text,
Img,
Section,
Link,
Head,
render,
Container,
} from "@react-email/components";
import type { InviteWithInviterAndOrg } from "~/modules/invite/types";
import { SERVER_URL } from "~/utils/env";
import { LogoForEmail } from "./logo";
import { styles } from "./styles";

interface Props {
Expand All @@ -24,18 +24,11 @@ export function InvitationEmailTemplate({ invite, token }: Props) {
<title>Invitation to join Shelf</title>
</Head>

<Section style={{ padding: "56px" }}>
<Img
src="cid:shelf-logo"
alt="Shelf's logo"
width="100"
height="32"
style={{ marginBottom: "24px" }}
/>
<Container style={{ padding: "32px 16px", maxWidth: "100%" }}>
<LogoForEmail />

<div style={{ paddingTop: "8px" }}>
<Text
style={{ marginBottom: "24px", fontSize: "16px", color: "#344054" }}
>
<Text style={{ marginBottom: "24px", ...styles.p }}>
Howdy,
<br />
{invite.inviter.firstName} {invite.inviter.lastName} invites you to
Expand All @@ -48,9 +41,7 @@ export function InvitationEmailTemplate({ invite, token }: Props) {
>
Accept the invite
</Button>
<Text
style={{ marginBottom: "24px", fontSize: "16px", color: "#344054" }}
>
<Text style={{ ...styles.p, marginBottom: "24px" }}>
Once you’re done setting up your account, you'll be able to access
the workspace and start exploring features like Asset Explorer,
Location Tracking, Collaboration, Custom fields and more. If you
Expand All @@ -61,9 +52,7 @@ export function InvitationEmailTemplate({ invite, token }: Props) {
</Link>
.
</Text>
<Text
style={{ marginBottom: "32px", fontSize: "16px", color: "#344054" }}
>
<Text style={{ marginBottom: "32px", ...styles.p }}>
Thanks, <br />
The Shelf team
</Text>
Expand All @@ -82,10 +71,14 @@ export function InvitationEmailTemplate({ invite, token }: Props) {
.
</Text>
</div>
</Section>
</Container>
</Html>
);
}

/*
*The HTML content of an email will be accessed by a server file to send email,
we cannot import a TSX component in a server file so we are exporting TSX converted to HTML string using render function by react-email.
*/
export const invitationTemplateString = ({ token, invite }: Props) =>
render(<InvitationEmailTemplate token={token} invite={invite} />);
25 changes: 25 additions & 0 deletions app/emails/logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Heading, Img } from "@react-email/components";

export function LogoForEmail() {
return (
<div style={{ margin: "0 auto", display: "flex" }}>
<Img
src="cid:shelf-logo"
alt="Shelf's logo"
width="32"
height="32"
style={{ marginRight: "6px", width: "32px", height: "32px" }}
/>
<Heading
as="h1"
style={{
color: "#101828",
fontWeight: "600",
margin: "0",
}}
>
shelf
</Heading>
</div>
);
}
13 changes: 13 additions & 0 deletions app/emails/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,17 @@ export const styles = {
padding: "10px 18px",
borderRadius: "4px",
},
h1: {
fontSize: "20px",
color: "#101828",
fontWeight: "600",
marginBottom: "16px",
},
h2: {
fontSize: "16px",
color: "#101828",
fontWeight: "600",
marginBottom: "16px",
},
p: { fontSize: "16px", color: "#344054" },
};
18 changes: 18 additions & 0 deletions app/emails/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Prisma } from "@prisma/client";

export type BookingForEmail = Prisma.BookingGetPayload<{
include: {
custodianTeamMember: true;
custodianUser: true;
organization: {
include: {
owner: {
select: { email: true };
};
};
};
_count: {
select: { assets: true };
};
};
}>;
33 changes: 20 additions & 13 deletions app/modules/booking/email-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Booking, TeamMember, User } from "@prisma/client";
import { bookingUpdatesTemplateString } from "~/emails/bookings-updates-template";
import type { BookingForEmail } from "~/emails/types";
import { SERVER_URL } from "~/utils";
import { getDateTimeFormatFromHints } from "~/utils/client-hints";
import { getTimeRemainingMessage } from "~/utils/date-fns";
Expand All @@ -9,7 +10,7 @@ import type { ClientHint } from "./types";
* THis is the base content of the bookings related emails.
* We always provide some general info so this function standardizes that.
*/
export const baseBookingEmailContent = ({
export const baseBookingTextEmailContent = ({
bookingName,
custodian,
from,
Expand Down Expand Up @@ -74,7 +75,7 @@ export const assetReservedEmailContent = ({
bookingId: string;
hints: ClientHint;
}) =>
baseBookingEmailContent({
baseBookingTextEmailContent({
hints,
bookingName,
custodian,
Expand Down Expand Up @@ -105,7 +106,7 @@ export const checkoutReminderEmailContent = ({
bookingId: string;
hints: ClientHint;
}) =>
baseBookingEmailContent({
baseBookingTextEmailContent({
hints,
bookingName,
custodian,
Expand Down Expand Up @@ -140,7 +141,7 @@ export const checkinReminderEmailContent = ({
bookingId: string;
hints: ClientHint;
}) =>
baseBookingEmailContent({
baseBookingTextEmailContent({
hints,
bookingName,
custodian,
Expand All @@ -155,10 +156,7 @@ export const checkinReminderEmailContent = ({
});

export const sendCheckinReminder = async (
booking: Booking & {
custodianTeamMember: TeamMember | null;
custodianUser: User | null;
},
booking: BookingForEmail,
assetCount: number,
hints: ClientHint
) => {
Expand All @@ -176,6 +174,15 @@ export const sendCheckinReminder = async (
to: booking.to!,
bookingId: booking.id,
}),
html: bookingUpdatesTemplateString({
booking,
heading: `Your booking is due for checkin in ${getTimeRemainingMessage(
new Date(booking.to!),
new Date()
)} minutes.`,
assetCount,
hints,
}),
});
};

Expand All @@ -201,7 +208,7 @@ export const overdueBookingEmailContent = ({
bookingId: string;
hints: ClientHint;
}) =>
baseBookingEmailContent({
baseBookingTextEmailContent({
hints,
bookingName,
custodian,
Expand Down Expand Up @@ -234,7 +241,7 @@ export const completedBookingEmailContent = ({
bookingId: string;
hints: ClientHint;
}) =>
baseBookingEmailContent({
baseBookingTextEmailContent({
hints,
bookingName,
custodian,
Expand Down Expand Up @@ -267,7 +274,7 @@ export const deletedBookingEmailContent = ({
bookingId: string;
hints: ClientHint;
}) =>
baseBookingEmailContent({
baseBookingTextEmailContent({
hints,
bookingName,
custodian,
Expand Down Expand Up @@ -300,7 +307,7 @@ export const cancelledBookingEmailContent = ({
bookingId: string;
hints: ClientHint;
}) =>
baseBookingEmailContent({
baseBookingTextEmailContent({
hints,
bookingName,
custodian,
Expand Down
Loading
Loading