Refactor emails to use JSX as templating engine (#2915)
* Init Maizzle * Initial template JSX conversion and testing * WIP * WIP * WIP * WIP * WIP * Migrated AttendeeRescheduledEmail * WIP * WIP * DRY * Cleanup * Cleanup * Cleanup * Migrate feedback email * Migrates ForgotPasswordEmail * Migrates OrganizerCancelledEmail * Migrated OrganizerLocationChangeEmail * Formatting * Migrated AttendeeRequestRescheduledEmail * Migrates OrganizerPaymentRefundFailedEmail * Migrates OrganizerRequestEmail * Migrates OrganizerRequestReminderEmail * Fixes type-check * Moved email-manager to package * Import fixes * Removed duplicate email code from vital app * Removed duplicate email code from wipemycal * Build/type fixes * Fixes web email imports * Fixes build * Embed build fixes * Update AttendeeAwaitingPaymentEmail.tsx * Update default-cookies.ts * Revert "Embed build fixes" This reverts commit 8d693e99aca6dfe92d5cbb27ffa962545c3e9389. * Embed build fixes # Conflicts: # packages/embeds/embed-core/package.json * dep and email date fixes * Update attendee-scheduled-email.ts * Update package.json * Update [...nextauth].tsx * Update email.ts * Prevents /api/email on production builds Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
ad101e0a31
commit
15d5333cf6
137 files changed
+2919
-6302
No files matched your search
@@ -4,20 +4,13 @@ import { v4 as uuidv4 } from "uuid";
|
||||
import { z } from "zod";
|
||||
|
||||
import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug";
|
||||
import { sendAwaitingPaymentEmail, sendOrganizerPaymentRefundFailedEmail } from "@calcom/emails";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { createPaymentLink } from "@calcom/stripe/client";
|
||||
import stripe, { PaymentData } from "@calcom/stripe/server";
|
||||
import { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { sendAwaitingPaymentEmail, sendOrganizerPaymentRefundFailedEmail } from "@lib/emails/email-manager";
|
||||
|
||||
export type PaymentInfo = {
|
||||
link?: string | null;
|
||||
reason?: string | null;
|
||||
id?: string | null;
|
||||
};
|
||||
|
||||
const stripeKeysSchema = z.object({
|
||||
payment_fee_fixed: z.number(),
|
||||
payment_fee_percentage: z.number(),
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import Stripe from "stripe";
|
||||
|
||||
import EventManager from "@calcom/core/EventManager";
|
||||
import { sendScheduledEmails } from "@calcom/emails";
|
||||
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
@@ -12,7 +13,6 @@ import { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { IS_PRODUCTION } from "@lib/config/constants";
|
||||
import { HttpError as HttpCode } from "@lib/core/http/error";
|
||||
import { sendScheduledEmails } from "@lib/emails/email-manager";
|
||||
|
||||
import { getTranslation } from "@server/lib/i18n";
|
||||
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
linkIcon,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class AttendeeAwaitingPaymentEmail extends AttendeeScheduledEmail {
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
return {
|
||||
to: `${this.attendee.name} <${this.attendee.email}>`,
|
||||
from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`,
|
||||
replyTo: this.calEvent.organizer.email,
|
||||
subject: `${this.attendee.language.translate("awaiting_payment_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.team?.name || this.calEvent.organizer.name,
|
||||
date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.attendee.language.translate("meeting_awaiting_payment")}
|
||||
${this.attendee.language.translate("emailed_you_and_any_other_attendees")}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.attendee.language.translate("awaiting_payment_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.team?.name || this.calEvent.organizer.name,
|
||||
date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.attendee.language.translate("meeting_awaiting_payment"),
|
||||
this.attendee.language.translate("emailed_you_and_any_other_attendees")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
|
||||
${this.getManageLink()}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getManageLink(): string {
|
||||
const manageText = this.attendee.language.translate("pay_now");
|
||||
|
||||
if (this.calEvent.paymentInfo) {
|
||||
return `
|
||||
<tr>
|
||||
<td align="center" bgcolor="#292929" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#292929;" valign="middle">
|
||||
<p style="display:inline-block;background:#292929;color:#ffffff;font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;">
|
||||
<a style="color: #FFFFFF; text-decoration: none;" href="${
|
||||
this.calEvent.paymentInfo.link
|
||||
}" target="_blank">${manageText} <img src="${linkIcon()}" width="12px"></img></a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class AttendeeCancelledEmail extends AttendeeScheduledEmail {
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
return {
|
||||
to: `${this.attendee.name} <${this.attendee.email}>`,
|
||||
from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`,
|
||||
replyTo: this.calEvent.organizer.email,
|
||||
subject: `${this.attendee.language.translate("event_cancelled_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.team?.name || this.calEvent.organizer.name,
|
||||
date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.attendee.language.translate("event_request_cancelled")}
|
||||
${this.attendee.language.translate("emailed_you_and_any_other_attendees")}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.attendee.language.translate("event_cancelled_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.team?.name || this.calEvent.organizer.name,
|
||||
date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("xCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.attendee.language.translate("event_request_cancelled"),
|
||||
this.attendee.language.translate("emailed_you_and_any_other_attendees")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getCancellationReason(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.attendee.language.translate("cancellation_reason")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.cancellationReason}</p>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class AttendeeDeclinedEmail extends AttendeeScheduledEmail {
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
return {
|
||||
to: `${this.attendee.name} <${this.attendee.email}>`,
|
||||
from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`,
|
||||
replyTo: this.calEvent.organizer.email,
|
||||
subject: `${this.attendee.language.translate("event_declined_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.team?.name || this.calEvent.organizer.name,
|
||||
date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.attendee.language.translate(
|
||||
this.recurringEvent?.count ? "event_request_declined_recurring" : "event_request_declined"
|
||||
)}
|
||||
${this.attendee.language.translate("emailed_you_and_any_other_attendees")}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.getRejectionReason()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.attendee.language.translate("event_declined_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.team?.name || this.calEvent.organizer.name,
|
||||
date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("xCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.attendee.language.translate(
|
||||
this.recurringEvent?.count ? "event_request_declined_recurring" : "event_request_declined"
|
||||
),
|
||||
this.attendee.language.translate("emailed_you_and_any_other_attendees")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.getRejectionReason()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
|
||||
import { getCancelLink } from "@calcom/lib/CalEventParser";
|
||||
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class AttendeeLocationChangeEmail extends AttendeeScheduledEmail {
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
to: `${this.attendee.name} <${this.attendee.email}>`,
|
||||
from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`,
|
||||
replyTo: this.calEvent.organizer.email,
|
||||
subject: `${this.attendee.language.translate("location_changed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.team?.name || this.calEvent.organizer.name,
|
||||
date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
protected getTextBody(): string {
|
||||
// Only the original attendee can make changes to the event
|
||||
// Guests cannot
|
||||
if (this.attendee === this.calEvent.attendees[0]) {
|
||||
return `
|
||||
${this.attendee.language.translate("event_location_changed")}
|
||||
${this.attendee.language.translate("emailed_you_and_any_other_attendees")}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.attendee.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
return `
|
||||
${this.attendee.language.translate("event_location_changed")}
|
||||
${this.attendee.language.translate("emailed_you_and_any_other_attendees")}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getAdditionalNotes()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.attendee.language.translate("location_changed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.team?.name || this.calEvent.organizer.name,
|
||||
date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.attendee.language.translate("event_location_changed"),
|
||||
this.attendee.language.translate("emailed_you_and_any_other_attendees")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:0px;text-align:left;color:#3E3E3E;">
|
||||
${this.getManageLink()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class AttendeeRequestEmail extends AttendeeScheduledEmail {
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.attendees[0].email];
|
||||
if (this.calEvent.team) {
|
||||
this.calEvent.team.members.forEach((member) => {
|
||||
const memberAttendee = this.calEvent.attendees.find((attendee) => attendee.name === member);
|
||||
if (memberAttendee) {
|
||||
toAddresses.push(memberAttendee.email);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("booking_submitted_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.attendees[0].language.translate("booking_submitted", {
|
||||
name: this.calEvent.attendees[0].name,
|
||||
})}
|
||||
${this.calEvent.attendees[0].language.translate("user_needs_to_confirm_or_reject_booking", {
|
||||
user: this.calEvent.organizer.name,
|
||||
})}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.attendees[0].language.translate("booking_submitted_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.attendees[0].language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.attendees[0].language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate(
|
||||
this.recurringEvent?.count ? "booking_submitted_recurring" : "booking_submitted"
|
||||
),
|
||||
this.calEvent.organizer.language.translate(
|
||||
this.recurringEvent.count
|
||||
? "user_needs_to_confirm_or_reject_booking_recurring"
|
||||
: "user_needs_to_confirm_or_reject_booking",
|
||||
{
|
||||
user: this.calEvent.organizer.name,
|
||||
}
|
||||
)
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { createEvent, DateArray, Person } from "ics";
|
||||
|
||||
import { getCancelLink } from "@calcom/lib/CalEventParser";
|
||||
import { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class AttendeeRequestRescheduledEmail extends OrganizerScheduledEmail {
|
||||
private metadata: { rescheduleLink: string };
|
||||
constructor(calEvent: CalendarEvent, metadata: { rescheduleLink: string }, recurringEvent: RecurringEvent) {
|
||||
super(calEvent, recurringEvent);
|
||||
this.metadata = metadata;
|
||||
}
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.attendees[0].email];
|
||||
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("requested_to_reschedule_subject_attendee", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
// @OVERRIDE
|
||||
protected getiCalEventAsString(): string | undefined {
|
||||
const icsEvent = createEvent({
|
||||
start: dayjs(this.calEvent.startTime)
|
||||
.utc()
|
||||
.toArray()
|
||||
.slice(0, 6)
|
||||
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
|
||||
startInputType: "utc",
|
||||
productId: "calendso/ics",
|
||||
title: this.calEvent.organizer.language.translate("ics_event_title", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
description: this.getTextBody(),
|
||||
duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") },
|
||||
organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email },
|
||||
attendees: this.calEvent.attendees.map((attendee: Person) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
})),
|
||||
status: "CANCELLED",
|
||||
method: "CANCEL",
|
||||
});
|
||||
if (icsEvent.error) {
|
||||
throw icsEvent.error;
|
||||
}
|
||||
return icsEvent.value;
|
||||
}
|
||||
// @OVERRIDE
|
||||
protected getWhen(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("when")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;text-decoration: line-through;">
|
||||
${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format(
|
||||
"YYYY"
|
||||
)} | ${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_title_attendee")}
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_subtitle", {
|
||||
organizer: this.calEvent.organizer.name,
|
||||
})},
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("request_reschedule_title_attendee"),
|
||||
this.calEvent.organizer.language.translate("request_reschedule_subtitle", {
|
||||
organizer: this.calEvent.organizer.name,
|
||||
})
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 40px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;text-align:center;color:#3E3E3E;">
|
||||
<a style="padding: 8px 16px;background-color: #292929;color: white;border-radius: 2px;display: inline-block;margin-bottom: 16px;"
|
||||
href="${this.metadata.rescheduleLink}" target="_blank"
|
||||
>
|
||||
Book a new time
|
||||
<img src="https://app.cal.com/emails/linkIcon.png" style="width:16px; margin-left: 5px;filter: brightness(0) invert(1); vertical-align: top;" />
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
|
||||
import { getCancelLink } from "@calcom/lib/CalEventParser";
|
||||
|
||||
import AttendeeScheduledEmail from "./attendee-scheduled-email";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class AttendeeRescheduledEmail extends AttendeeScheduledEmail {
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
to: `${this.attendee.name} <${this.attendee.email}>`,
|
||||
from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`,
|
||||
replyTo: this.calEvent.organizer.email,
|
||||
subject: `${this.attendee.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.team?.name || this.calEvent.organizer.name,
|
||||
date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
protected getTextBody(): string {
|
||||
// Only the original attendee can make changes to the event
|
||||
// Guests cannot
|
||||
if (this.attendee === this.calEvent.attendees[0]) {
|
||||
return `
|
||||
${this.attendee.language.translate("event_has_been_rescheduled")}
|
||||
${this.attendee.language.translate("emailed_you_and_any_other_attendees")}
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.attendee.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
return `
|
||||
${this.attendee.language.translate("event_has_been_rescheduled")}
|
||||
${this.attendee.language.translate("emailed_you_and_any_other_attendees")}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.attendee.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.team?.name || this.calEvent.organizer.name,
|
||||
date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.attendee.language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.attendee.language.translate("event_has_been_rescheduled"),
|
||||
this.attendee.language.translate("emailed_you_and_any_other_attendees")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:0px;text-align:left;color:#3E3E3E;">
|
||||
${this.getManageLink()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,463 +0,0 @@
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { createEvent, DateArray } from "ics";
|
||||
import rrule from "rrule";
|
||||
|
||||
import { getAppName } from "@calcom/app-store/utils";
|
||||
import { getCancelLink, getRichDescription } from "@calcom/lib/CalEventParser";
|
||||
import type { CalendarEvent, Person, RecurringEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import BaseEmail from "@lib/emails/templates/_base-email";
|
||||
|
||||
import {
|
||||
emailBodyLogo,
|
||||
emailHead,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
emailSchedulingBodyHeader,
|
||||
linkIcon,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class AttendeeScheduledEmail extends BaseEmail {
|
||||
calEvent: CalendarEvent;
|
||||
attendee: Person;
|
||||
recurringEvent: RecurringEvent;
|
||||
|
||||
constructor(calEvent: CalendarEvent, attendee: Person, recurringEvent: RecurringEvent) {
|
||||
super();
|
||||
this.name = "SEND_BOOKING_CONFIRMATION";
|
||||
this.calEvent = calEvent;
|
||||
this.attendee = attendee;
|
||||
this.recurringEvent = recurringEvent;
|
||||
}
|
||||
|
||||
protected getiCalEventAsString(): string | undefined {
|
||||
// Taking care of recurrence rule beforehand
|
||||
let recurrenceRule: string | undefined = undefined;
|
||||
if (this.recurringEvent?.count) {
|
||||
recurrenceRule = new rrule(this.recurringEvent).toString().replace("RRULE:", "");
|
||||
}
|
||||
const icsEvent = createEvent({
|
||||
start: dayjs(this.calEvent.startTime)
|
||||
.utc()
|
||||
.toArray()
|
||||
.slice(0, 6)
|
||||
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
|
||||
startInputType: "utc",
|
||||
productId: "calendso/ics",
|
||||
title: this.calEvent.attendees[0].language.translate("ics_event_title", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
description: this.getTextBody(),
|
||||
duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") },
|
||||
organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email },
|
||||
attendees: this.calEvent.attendees.map((attendee: Person) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
})),
|
||||
...{ recurrenceRule },
|
||||
status: "CONFIRMED",
|
||||
});
|
||||
if (icsEvent.error) {
|
||||
throw icsEvent.error;
|
||||
}
|
||||
return icsEvent.value;
|
||||
}
|
||||
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
to: `${this.attendee.name} <${this.attendee.email}>`,
|
||||
from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`,
|
||||
replyTo: this.calEvent.organizer.email,
|
||||
subject: `${this.calEvent.attendees[0].language.translate("confirmed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.team?.name || this.calEvent.organizer.name,
|
||||
date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.attendees[0].language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.attendees[0].language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getDescription(): string {
|
||||
if (!this.calEvent.description) return "";
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("description")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px; white-space: pre-wrap;">${
|
||||
this.calEvent.description
|
||||
}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.attendees[0].language.translate(
|
||||
this.recurringEvent?.count ? "your_event_has_been_scheduled_recurring" : "your_event_has_been_scheduled"
|
||||
)}
|
||||
${this.calEvent.attendees[0].language.translate("emailed_you_and_any_other_attendees")}
|
||||
|
||||
${getRichDescription(this.calEvent)}
|
||||
`.trim();
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.attendees[0].language.translate("confirmed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.team?.name || this.calEvent.organizer.name,
|
||||
date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.attendees[0].language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.attendees[0].language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("checkCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.attendees[0].language.translate(
|
||||
this.recurringEvent?.count
|
||||
? "your_event_has_been_scheduled_recurring"
|
||||
: "your_event_has_been_scheduled"
|
||||
),
|
||||
this.calEvent.attendees[0].language.translate("emailed_you_and_any_other_attendees")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:0px;text-align:left;color:#3E3E3E;">
|
||||
${this.getManageLink()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getManageLink(): string {
|
||||
// Only the original attendee can make changes to the event
|
||||
// Guests cannot
|
||||
if (this.attendee === this.calEvent.attendees[0]) {
|
||||
const manageText = this.calEvent.attendees[0].language.translate("manage_this_event");
|
||||
return `<p>${this.calEvent.attendees[0].language.translate(
|
||||
"need_to_reschedule_or_cancel"
|
||||
)}</p><p style="font-weight: 400; line-height: 24px;"><a href="${getCancelLink(
|
||||
this.calEvent
|
||||
)}" style="color: #3E3E3E;" alt="${manageText}">${manageText}</a></p>`;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
protected getWhat(): string {
|
||||
return `
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.attendees[0].language.translate("what")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.type}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getRecurringWhen(): string {
|
||||
if (this.recurringEvent?.freq) {
|
||||
return ` - ${this.calEvent.attendees[0].language.translate("every_for_freq", {
|
||||
freq: this.calEvent.attendees[0].language.translate(
|
||||
`${rrule.FREQUENCIES[this.recurringEvent.freq].toString().toLowerCase()}`
|
||||
),
|
||||
})} ${this.recurringEvent.count} ${this.calEvent.attendees[0].language.translate(
|
||||
`${rrule.FREQUENCIES[this.recurringEvent.freq].toString().toLowerCase()}`,
|
||||
{ count: this.recurringEvent.count }
|
||||
)}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
protected getWhen(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.attendees[0].language.translate("when")}${
|
||||
this.recurringEvent?.count ? this.getRecurringWhen() : ""
|
||||
}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">
|
||||
${this.recurringEvent?.count ? `${this.calEvent.attendees[0].language.translate("starting")} ` : ""}
|
||||
${this.calEvent.attendees[0].language.translate(
|
||||
this.getInviteeStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.attendees[0].language.translate(
|
||||
this.getInviteeStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format(
|
||||
"YYYY"
|
||||
)} | ${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format(
|
||||
"h:mma"
|
||||
)} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getWho(): string {
|
||||
const attendees = this.calEvent.attendees
|
||||
.map((attendee) => {
|
||||
return `<div style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
attendee?.name || `${this.calEvent.attendees[0].language.translate("guest")}`
|
||||
} <span style="color: #888888"><a href="mailto:${attendee.email}" style="color: #888888;">${
|
||||
attendee.email
|
||||
}</a></span></div>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
const organizer = `<div style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
this.calEvent.organizer.name
|
||||
} - ${this.calEvent.attendees[0].language.translate(
|
||||
"organizer"
|
||||
)} <span style="color: #888888"><a href="mailto:${
|
||||
this.calEvent.organizer.email
|
||||
}" style="color: #888888;">${this.calEvent.organizer.email}</a></span></div>`;
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.attendees[0].language.translate("who")}</p>
|
||||
${organizer + attendees}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getAdditionalNotes(): string {
|
||||
if (!this.calEvent.additionalNotes) return "";
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.attendees[0].language.translate("additional_notes")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px; white-space: pre-wrap;">${
|
||||
this.calEvent.additionalNotes
|
||||
}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getCustomInputs(): string {
|
||||
const { customInputs } = this.calEvent;
|
||||
if (!customInputs) return "";
|
||||
const customInputsString = Object.keys(customInputs)
|
||||
.map((key) => {
|
||||
if (customInputs[key] !== "") {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${key}</p>
|
||||
<p style="color: #494949; font-weight: 400;">
|
||||
${customInputs[key]}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
})
|
||||
.join("");
|
||||
|
||||
return customInputsString;
|
||||
}
|
||||
|
||||
protected getRejectionReason(): string {
|
||||
if (!this.calEvent.rejectionReason) return "";
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.attendees[0].language.translate("rejection_reason")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.rejectionReason}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getLocation(): string {
|
||||
let providerName = this.calEvent.location && getAppName(this.calEvent.location);
|
||||
|
||||
if (this.calEvent.location && this.calEvent.location.includes("integrations:")) {
|
||||
const location = this.calEvent.location.split(":")[1];
|
||||
providerName = location[0].toUpperCase() + location.slice(1);
|
||||
}
|
||||
|
||||
// If location its a url, probably we should be validating it with a custom library
|
||||
if (this.calEvent.location && /^https?:\/\//.test(this.calEvent.location)) {
|
||||
providerName = this.calEvent.location;
|
||||
}
|
||||
|
||||
if (this.calEvent.videoCallData) {
|
||||
const meetingId = this.calEvent.videoCallData.id;
|
||||
const meetingPassword = this.calEvent.videoCallData.password;
|
||||
const meetingUrl = this.calEvent.videoCallData.url;
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.attendees[0].language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${providerName} ${
|
||||
meetingUrl &&
|
||||
`<a href="${meetingUrl}" target="_blank" alt="${this.calEvent.attendees[0].language.translate(
|
||||
"meeting_url"
|
||||
)}"><img src="${linkIcon()}" width="12px"></img></a>`
|
||||
}</p>
|
||||
${
|
||||
meetingId &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.attendees[0].language.translate(
|
||||
"meeting_id"
|
||||
)}: <span>${meetingId}</span></div>`
|
||||
}
|
||||
${
|
||||
meetingPassword &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.attendees[0].language.translate(
|
||||
"meeting_password"
|
||||
)}: <span>${meetingPassword}</span></div>`
|
||||
}
|
||||
${
|
||||
meetingUrl &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.attendees[0].language.translate(
|
||||
"meeting_url"
|
||||
)}: <a href="${meetingUrl}" alt="${this.calEvent.attendees[0].language.translate(
|
||||
"meeting_url"
|
||||
)}" style="color: #3E3E3E" target="_blank">${meetingUrl}</a></div>`
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this.calEvent.additionInformation?.hangoutLink) {
|
||||
const hangoutLink: string = this.calEvent.additionInformation.hangoutLink;
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.attendees[0].language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${providerName} ${
|
||||
hangoutLink &&
|
||||
`<a href="${hangoutLink}" target="_blank" alt="${this.calEvent.attendees[0].language.translate(
|
||||
"meeting_url"
|
||||
)}"><img src="${linkIcon()}" width="12px"></img></a>`
|
||||
}</p>
|
||||
<div style="color: #494949; font-weight: 400; line-height: 24px;"><a href="${hangoutLink}" alt="${this.calEvent.attendees[0].language.translate(
|
||||
"meeting_url"
|
||||
)}" style="color: #3E3E3E" target="_blank">${hangoutLink}</a></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.attendees[0].language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
providerName || this.calEvent.location
|
||||
}</p>
|
||||
${
|
||||
providerName === "Zoom" || providerName === "Google"
|
||||
? `<p style="color: #494949; font-weight: 400; line-height: 24px;">
|
||||
${this.calEvent.organizer.language.translate("meeting_url_provided_after_confirmed")}
|
||||
</p>`
|
||||
: ``
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getTimezone(): string {
|
||||
// Timezone is based on the first attendee in the attendee list
|
||||
// as the first attendee is the one who created the booking
|
||||
return this.calEvent.attendees[0].timeZone;
|
||||
}
|
||||
|
||||
protected getInviteeStart(): Dayjs {
|
||||
return dayjs(this.calEvent.startTime).tz(this.getTimezone());
|
||||
}
|
||||
|
||||
protected getInviteeEnd(): Dayjs {
|
||||
return dayjs(this.calEvent.endTime).tz(this.getTimezone());
|
||||
}
|
||||
|
||||
protected getReason(): string {
|
||||
return `
|
||||
<div style="line-height: 6px; margin-bottom: 24px;">
|
||||
<p style="color: #494949;">${this.calEvent.attendees[0].language.translate("reschedule_reason")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.cancellationReason}</p>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { IS_PRODUCTION, BASE_URL } from "@lib/config/constants";
|
||||
|
||||
export const emailBodyLogo = (): string => {
|
||||
const image = IS_PRODUCTION
|
||||
? BASE_URL + "/emails/CalLogo@2x.png"
|
||||
: "https://app.cal.com/emails/CalLogo@2x.png";
|
||||
|
||||
return `
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-top:32px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:89px;">
|
||||
<a href="${BASE_URL}" target="_blank">
|
||||
<img height="19" src="${image}" style="border:0;display:block;outline:none;text-decoration:none;height:19px;width:100%;font-size:13px;" width="89" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
@@ -1,71 +0,0 @@
|
||||
import { IS_PRODUCTION, BASE_URL } from "@lib/config/constants";
|
||||
|
||||
export type BodyHeadType = "checkCircle" | "xCircle" | "calendarCircle";
|
||||
|
||||
export const getHeadImage = (headerType: BodyHeadType): string => {
|
||||
switch (headerType) {
|
||||
case "checkCircle":
|
||||
return IS_PRODUCTION
|
||||
? BASE_URL + "/emails/checkCircle@2x.png"
|
||||
: "https://app.cal.com/emails/checkCircle@2x.png";
|
||||
case "xCircle":
|
||||
return IS_PRODUCTION
|
||||
? BASE_URL + "/emails/xCircle@2x.png"
|
||||
: "https://app.cal.com/emails/xCircle@2x.png";
|
||||
case "calendarCircle":
|
||||
return IS_PRODUCTION
|
||||
? BASE_URL + "/emails/calendarCircle@2x.png"
|
||||
: "https://app.cal.com/emails/calendarCircle@2x.png";
|
||||
}
|
||||
};
|
||||
|
||||
export const emailSchedulingBodyHeader = (headerType: BodyHeadType): string => {
|
||||
const image = getHeadImage(headerType);
|
||||
|
||||
return `
|
||||
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:0px;padding-top:40px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;border-top:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:30px 30px 0 30px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:558px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:64px;">
|
||||
<img height="64" src="${image}" style="border:0;display:block;outline:none;text-decoration:none;height:64px;width:100%;font-size:13px;" width="64" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
@@ -1,118 +0,0 @@
|
||||
import BaseEmail from "@lib/emails/templates/_base-email";
|
||||
|
||||
import { emailHead, emailBodyLogo } from "./common";
|
||||
|
||||
export interface Feedback {
|
||||
userId: number;
|
||||
rating: string;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
export default class FeedbackEmail extends BaseEmail {
|
||||
feedback: Feedback;
|
||||
|
||||
constructor(feedback: Feedback) {
|
||||
super();
|
||||
this.feedback = feedback;
|
||||
}
|
||||
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
return {
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: process.env.SEND_FEEDBACK_EMAIL,
|
||||
subject: `User Feedback`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
userId: ${this.feedback.userId}
|
||||
rating: ${this.feedback.rating}
|
||||
comment: ${this.feedback.comment}
|
||||
`;
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead("Feedback")}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-top:24px;padding-bottom:0px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:24px;font-weight:700;line-height:24px;text-align:center;color:#292929;">Feedback</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="background-color:#F5F5F5;">
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">Used id</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
this.feedback.userId
|
||||
}</p>
|
||||
</div>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">Rating</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
this.feedback.rating
|
||||
}</p>
|
||||
</div>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">Comment</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
this.feedback.comment
|
||||
}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
import { TFunction } from "next-i18next";
|
||||
|
||||
import BaseEmail from "@lib/emails/templates/_base-email";
|
||||
|
||||
import { emailBodyLogo, emailHead, linkIcon } from "./common";
|
||||
|
||||
export type PasswordReset = {
|
||||
language: TFunction;
|
||||
user: {
|
||||
name?: string | null;
|
||||
email: string;
|
||||
};
|
||||
resetLink: string;
|
||||
};
|
||||
|
||||
export const PASSWORD_RESET_EXPIRY_HOURS = 6;
|
||||
|
||||
export default class ForgotPasswordEmail extends BaseEmail {
|
||||
passwordEvent: PasswordReset;
|
||||
|
||||
constructor(passwordEvent: PasswordReset) {
|
||||
super();
|
||||
this.name = "SEND_PASSWORD_RESET_EMAIL";
|
||||
this.passwordEvent = passwordEvent;
|
||||
}
|
||||
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
return {
|
||||
to: `${this.passwordEvent.user.name} <${this.passwordEvent.user.email}>`,
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
subject: this.passwordEvent.language("reset_password_subject"),
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.passwordEvent.language("reset_password_subject")}
|
||||
${this.passwordEvent.language("hi_user_name", { user: this.passwordEvent.user.name })},
|
||||
${this.passwordEvent.language("someone_requested_password_reset")}
|
||||
${this.passwordEvent.language("change_password")}: ${this.passwordEvent.resetLink}
|
||||
${this.passwordEvent.language("password_reset_instructions")}
|
||||
${this.passwordEvent.language("have_any_questions")} ${this.passwordEvent.language(
|
||||
"contact_our_support_team"
|
||||
)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.passwordEvent.language("reset_password_subject");
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:0px;padding-top:40px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;border-top:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:30px 20px 0 20px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
<div style="line-height: 6px;">
|
||||
<p>${this.passwordEvent.language("hi_user_name", {
|
||||
user: this.passwordEvent.user.name,
|
||||
})},</p>
|
||||
<p style="font-weight: 400; line-height: 24px;">${this.passwordEvent.language(
|
||||
"someone_requested_password_reset"
|
||||
)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
|
||||
<tr>
|
||||
<td align="center" bgcolor="#292929" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#292929;" valign="middle">
|
||||
<p style="display:inline-block;background:#292929;color:#292929;font-family:Roboto, Helvetica, sans-serif;font-size:13px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;">
|
||||
<a href="${
|
||||
this.passwordEvent.resetLink
|
||||
}" target="_blank" style="color: #FFFFFF; text-decoration: none">${this.passwordEvent.language(
|
||||
"change_password"
|
||||
)} <img src="${linkIcon()}" width="12px"></img></a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
<div style="line-height: 6px;">
|
||||
<p style="font-weight: 400; line-height: 24px;">${this.passwordEvent.language(
|
||||
"password_reset_instructions"
|
||||
)}</p>
|
||||
</div>
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="font-weight: 400; line-height: 24px;">${this.passwordEvent.language(
|
||||
"have_any_questions"
|
||||
)} <a href="mailto:support@cal.com" style="color: #3E3E3E" target="_blank">${this.passwordEvent.language(
|
||||
"contact_our_support_team"
|
||||
)}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class OrganizerCancelledEmail extends OrganizerScheduledEmail {
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.organizer.email];
|
||||
if (this.calEvent.team) {
|
||||
this.calEvent.team.members.forEach((member) => {
|
||||
const memberAttendee = this.calEvent.attendees.find((attendee) => attendee.name === member);
|
||||
if (memberAttendee) {
|
||||
toAddresses.push(memberAttendee.email);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("event_cancelled_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("event_request_cancelled")}
|
||||
${this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("event_cancelled_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("xCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("event_request_cancelled"),
|
||||
this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getCancellationReason(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("cancellation_reason")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.cancellationReason}</p>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
|
||||
import { getCancelLink } from "@calcom/lib/CalEventParser";
|
||||
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class OrganizerLocationChangeEmail extends OrganizerScheduledEmail {
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.organizer.email];
|
||||
if (this.calEvent.team) {
|
||||
this.calEvent.team.members.forEach((member) => {
|
||||
const memberAttendee = this.calEvent.attendees.find((attendee) => attendee.name === member);
|
||||
if (memberAttendee) {
|
||||
toAddresses.push(memberAttendee.email);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("location_changed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("event_location_changed")}
|
||||
${this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("location_changed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("event_location_changed"),
|
||||
this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:0px;text-align:left;color:#3E3E3E;">
|
||||
${this.getManageLink()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
|
||||
import { emailHead, emailSchedulingBodyHeader, emailBodyLogo, emailSchedulingBodyDivider } from "./common";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class OrganizerPaymentRefundFailedEmail extends OrganizerScheduledEmail {
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.organizer.email];
|
||||
if (this.calEvent.team) {
|
||||
this.calEvent.team.members.forEach((member) => {
|
||||
const memberAttendee = this.calEvent.attendees.find((attendee) => attendee.name === member);
|
||||
if (memberAttendee) {
|
||||
toAddresses.push(memberAttendee.email);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("refund_failed_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("a_refund_failed")}
|
||||
${this.calEvent.organizer.language.translate("check_with_provider_and_user", {
|
||||
user: this.calEvent.attendees[0].name,
|
||||
})}
|
||||
${
|
||||
this.calEvent.paymentInfo &&
|
||||
this.calEvent.organizer.language.translate("error_message", {
|
||||
errorMessage: this.calEvent.paymentInfo.reason,
|
||||
})
|
||||
}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("refund_failed_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("xCircle")}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-top:24px;padding-bottom:0px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:24px;font-weight:700;line-height:24px;text-align:center;color:#292929;">${this.calEvent.organizer.language.translate(
|
||||
"a_refund_failed"
|
||||
)}</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:400;line-height:24px;text-align:center;color:#494949;">${this.calEvent.organizer.language.translate(
|
||||
"check_with_provider_and_user",
|
||||
{ user: this.calEvent.attendees[0].name }
|
||||
)}</div>
|
||||
</td>
|
||||
</tr>
|
||||
${this.getRefundInformation()}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getRefundInformation(): string {
|
||||
const paymentInfo = this.calEvent.paymentInfo;
|
||||
let refundInformation = "";
|
||||
|
||||
if (paymentInfo) {
|
||||
if (paymentInfo.reason) {
|
||||
refundInformation = `
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:400;line-height:24px;text-align:center;color:#494949;">${this.calEvent.organizer.language.translate(
|
||||
"error_message",
|
||||
{ errorMessage: paymentInfo.reason }
|
||||
)}</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
if (paymentInfo.id) {
|
||||
refundInformation += `
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:400;line-height:24px;text-align:center;color:#494949;">Payment ${paymentInfo.id}</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
return refundInformation;
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
linkIcon,
|
||||
} from "./common";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class OrganizerRequestEmail extends OrganizerScheduledEmail {
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.organizer.email];
|
||||
if (this.calEvent.team) {
|
||||
this.calEvent.team.members.forEach((member) => {
|
||||
const memberAttendee = this.calEvent.attendees.find((attendee) => attendee.name === member);
|
||||
if (memberAttendee) {
|
||||
toAddresses.push(memberAttendee.email);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("event_awaiting_approval_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("event_awaiting_approval")}
|
||||
${this.calEvent.organizer.language.translate("someone_requested_an_event")}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.organizer.language.translate("confirm_or_reject_request")}
|
||||
${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("event_awaiting_approval_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate(
|
||||
this.recurringEvent?.count ? "event_awaiting_approval_recurring" : "event_awaiting_approval"
|
||||
),
|
||||
this.calEvent.organizer.language.translate("someone_requested_an_event")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
|
||||
<tr>
|
||||
<td align="center" bgcolor="#292929" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#292929;" valign="middle">
|
||||
<p style="display:inline-block;background:#292929;color:#ffffff;font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;">
|
||||
${this.getManageLink()}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getManageLink(): string {
|
||||
const manageText = this.calEvent.organizer.language.translate("confirm_or_reject_request");
|
||||
const manageLink = process.env.NEXT_PUBLIC_WEBAPP_URL + "/bookings/upcoming";
|
||||
return `<a style="color: #FFFFFF; text-decoration: none;" href="${manageLink}" target="_blank">${manageText} <img src="${linkIcon()}" width="12px"></img></a>`;
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
linkIcon,
|
||||
} from "./common";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class OrganizerRequestReminderEmail extends OrganizerScheduledEmail {
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.organizer.email];
|
||||
if (this.calEvent.team) {
|
||||
this.calEvent.team.members.forEach((member) => {
|
||||
const memberAttendee = this.calEvent.attendees.find((attendee) => attendee.name === member);
|
||||
if (memberAttendee) {
|
||||
toAddresses.push(memberAttendee.email);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("event_awaiting_approval_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("event_still_awaiting_approval")}
|
||||
${this.calEvent.organizer.language.translate("someone_requested_an_event")}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.organizer.language.translate("confirm_or_reject_request")}
|
||||
${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("event_awaiting_approval_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("event_still_awaiting_approval"),
|
||||
this.calEvent.organizer.language.translate("someone_requested_an_event")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
|
||||
<tr>
|
||||
<td align="center" bgcolor="#292929" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#292929;" valign="middle">
|
||||
<p style="display:inline-block;background:#292929;color:#ffffff;font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;">
|
||||
${this.getManageLink()}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getManageLink(): string {
|
||||
const manageText = this.calEvent.organizer.language.translate("confirm_or_reject_request");
|
||||
const manageLink = process.env.NEXT_PUBLIC_WEBAPP_URL + "/bookings/upcoming";
|
||||
return `<a style="color: #FFFFFF; text-decoration: none;" href="${manageLink}" target="_blank">${manageText} <img src="${linkIcon()}" width="12px"></img></a>`;
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { createEvent, DateArray, Person } from "ics";
|
||||
|
||||
import { getCancelLink } from "@calcom/lib/CalEventParser";
|
||||
import { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class OrganizerRequestRescheduledEmail extends OrganizerScheduledEmail {
|
||||
private metadata: { rescheduleLink: string };
|
||||
constructor(calEvent: CalendarEvent, metadata: { rescheduleLink: string }, recurringEvent: RecurringEvent) {
|
||||
super(calEvent, recurringEvent);
|
||||
this.metadata = metadata;
|
||||
}
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.organizer.email];
|
||||
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
// @OVERRIDE
|
||||
protected getiCalEventAsString(): string | undefined {
|
||||
const icsEvent = createEvent({
|
||||
start: dayjs(this.calEvent.startTime)
|
||||
.utc()
|
||||
.toArray()
|
||||
.slice(0, 6)
|
||||
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
|
||||
startInputType: "utc",
|
||||
productId: "calendso/ics",
|
||||
title: this.calEvent.organizer.language.translate("ics_event_title", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
description: this.getTextBody(),
|
||||
duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") },
|
||||
organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email },
|
||||
attendees: this.calEvent.attendees.map((attendee: Person) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
})),
|
||||
status: "CANCELLED",
|
||||
method: "CANCEL",
|
||||
});
|
||||
if (icsEvent.error) {
|
||||
throw icsEvent.error;
|
||||
}
|
||||
return icsEvent.value;
|
||||
}
|
||||
// @OVERRIDE
|
||||
protected getWhen(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("when")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;text-decoration: line-through;">
|
||||
${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format(
|
||||
"YYYY"
|
||||
)} | ${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_title_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
})}
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_subtitle_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
})},
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("request_reschedule_title_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
this.calEvent.organizer.language.translate("request_reschedule_subtitle_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
})
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 40px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
|
||||
import { getCancelLink } from "@calcom/lib/CalEventParser";
|
||||
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class OrganizerRescheduledEmail extends OrganizerScheduledEmail {
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.organizer.email];
|
||||
if (this.calEvent.team) {
|
||||
this.calEvent.team.members.forEach((member) => {
|
||||
const memberAttendee = this.calEvent.attendees.find((attendee) => attendee.name === member);
|
||||
if (memberAttendee) {
|
||||
toAddresses.push(memberAttendee.email);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("event_has_been_rescheduled")}
|
||||
${this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")}
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("event_has_been_rescheduled"),
|
||||
this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:0px;text-align:left;color:#3E3E3E;">
|
||||
${this.getManageLink()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,450 +0,0 @@
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { createEvent, DateArray, Person } from "ics";
|
||||
import rrule from "rrule";
|
||||
|
||||
import { getAppName } from "@calcom/app-store/utils";
|
||||
import { getCancelLink, getRichDescription } from "@calcom/lib/CalEventParser";
|
||||
import type { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import BaseEmail from "@lib/emails/templates/_base-email";
|
||||
|
||||
import {
|
||||
emailBodyLogo,
|
||||
emailHead,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
emailSchedulingBodyHeader,
|
||||
linkIcon,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class OrganizerScheduledEmail extends BaseEmail {
|
||||
calEvent: CalendarEvent;
|
||||
recurringEvent: RecurringEvent;
|
||||
|
||||
constructor(calEvent: CalendarEvent, recurringEvent: RecurringEvent) {
|
||||
super();
|
||||
this.name = "SEND_BOOKING_CONFIRMATION";
|
||||
this.calEvent = calEvent;
|
||||
this.recurringEvent = recurringEvent;
|
||||
}
|
||||
|
||||
protected getiCalEventAsString(): string | undefined {
|
||||
// Taking care of recurrence rule beforehand
|
||||
let recurrenceRule: string | undefined = undefined;
|
||||
if (this.recurringEvent?.count) {
|
||||
recurrenceRule = new rrule(this.recurringEvent).toString().replace("RRULE:", "");
|
||||
}
|
||||
const icsEvent = createEvent({
|
||||
start: dayjs(this.calEvent.startTime)
|
||||
.utc()
|
||||
.toArray()
|
||||
.slice(0, 6)
|
||||
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
|
||||
startInputType: "utc",
|
||||
productId: "calendso/ics",
|
||||
title: this.calEvent.organizer.language.translate("ics_event_title", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
description: this.getTextBody(),
|
||||
duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") },
|
||||
organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email },
|
||||
...{ recurrenceRule },
|
||||
attendees: this.calEvent.attendees.map((attendee: Person) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
})),
|
||||
status: "CONFIRMED",
|
||||
});
|
||||
if (icsEvent.error) {
|
||||
throw icsEvent.error;
|
||||
}
|
||||
return icsEvent.value;
|
||||
}
|
||||
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.organizer.email];
|
||||
if (this.calEvent.team) {
|
||||
this.calEvent.team.members.forEach((member) => {
|
||||
const memberAttendee = this.calEvent.attendees.find((attendee) => attendee.name === member);
|
||||
if (memberAttendee) {
|
||||
toAddresses.push(memberAttendee.email);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("confirmed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate(
|
||||
this.recurringEvent?.count ? "new_event_scheduled_recurring" : "new_event_scheduled"
|
||||
)}
|
||||
${this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")}
|
||||
|
||||
${getRichDescription(this.calEvent)}
|
||||
`.trim();
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("confirmed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("checkCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate(
|
||||
this.recurringEvent?.count ? "new_event_scheduled_recurring" : "new_event_scheduled"
|
||||
),
|
||||
this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.getCustomInputs()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:0px;text-align:left;color:#3E3E3E;">
|
||||
${this.getManageLink()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getManageLink(): string {
|
||||
const manageText = this.calEvent.organizer.language.translate("manage_this_event");
|
||||
return `<p>${this.calEvent.organizer.language.translate(
|
||||
"need_to_reschedule_or_cancel"
|
||||
)}</p><p style="font-weight: 400; line-height: 24px;"><a href="${getCancelLink(
|
||||
this.calEvent
|
||||
)}" style="color: #3E3E3E;" alt="${manageText}">${manageText}</a></p>`;
|
||||
}
|
||||
|
||||
protected getWhat(): string {
|
||||
return `
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("what")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.type}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getRecurringWhen(): string {
|
||||
if (this.recurringEvent?.freq) {
|
||||
return ` - ${this.calEvent.attendees[0].language.translate("every_for_freq", {
|
||||
freq: this.calEvent.attendees[0].language.translate(
|
||||
`${rrule.FREQUENCIES[this.recurringEvent.freq].toString().toLowerCase()}`
|
||||
),
|
||||
})} ${this.recurringEvent.count} ${this.calEvent.attendees[0].language.translate(
|
||||
`${rrule.FREQUENCIES[this.recurringEvent.freq].toString().toLowerCase()}`,
|
||||
{ count: this.recurringEvent.count }
|
||||
)}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
protected getWhen(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("when")}${
|
||||
this.recurringEvent?.count ? this.getRecurringWhen() : ""
|
||||
}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">
|
||||
${this.recurringEvent?.count ? `${this.calEvent.attendees[0].language.translate("starting")} ` : ""}
|
||||
${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format(
|
||||
"YYYY"
|
||||
)} | ${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getWho(): string {
|
||||
const attendees = this.calEvent.attendees
|
||||
.map((attendee) => {
|
||||
return `<div style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
attendee?.name || `${this.calEvent.organizer.language.translate("guest")}`
|
||||
} <span style="color: #888888"><a href="mailto:${attendee.email}" style="color: #888888;">${
|
||||
attendee.email
|
||||
}</a></span></div>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
const organizer = `<div style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
this.calEvent.organizer.name
|
||||
} - ${this.calEvent.organizer.language.translate(
|
||||
"organizer"
|
||||
)} <span style="color: #888888"><a href="mailto:${
|
||||
this.calEvent.organizer.email
|
||||
}" style="color: #888888;">${this.calEvent.organizer.email}</a></span></div>`;
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("who")}</p>
|
||||
${organizer + attendees}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getAdditionalNotes(): string {
|
||||
if (!this.calEvent.additionalNotes) return "";
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("additional_notes")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px; white-space: pre-wrap;">${
|
||||
this.calEvent.additionalNotes
|
||||
}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getCustomInputs(): string {
|
||||
const { customInputs } = this.calEvent;
|
||||
if (!customInputs) return "";
|
||||
const customInputsString = Object.keys(customInputs)
|
||||
.map((key) => {
|
||||
if (customInputs[key] !== "") {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${key}</p>
|
||||
<p style="color: #494949; font-weight: 400;">
|
||||
${customInputs[key]}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
})
|
||||
.join("");
|
||||
|
||||
return customInputsString;
|
||||
}
|
||||
|
||||
protected getDescription(): string {
|
||||
if (!this.calEvent.description) return "";
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("description")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px; white-space: pre-wrap;">${
|
||||
this.calEvent.description
|
||||
}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getLocation(): string {
|
||||
let providerName = this.calEvent.location && getAppName(this.calEvent.location); // This returns null if nothing is found
|
||||
|
||||
if (this.calEvent.location && this.calEvent.location.includes("integrations:")) {
|
||||
const location = this.calEvent.location.split(":")[1];
|
||||
providerName = location[0].toUpperCase() + location.slice(1);
|
||||
}
|
||||
|
||||
// If location its a url, probably we should be validating it with a custom library
|
||||
if (this.calEvent.location && /^https?:\/\//.test(this.calEvent.location)) {
|
||||
providerName = this.calEvent.location;
|
||||
}
|
||||
|
||||
if (this.calEvent.videoCallData) {
|
||||
const meetingId = this.calEvent.videoCallData.id;
|
||||
const meetingPassword = this.calEvent.videoCallData.password;
|
||||
const meetingUrl = this.calEvent.videoCallData.url;
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${providerName}
|
||||
${
|
||||
meetingUrl &&
|
||||
`<a href="${meetingUrl}" target="_blank" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}"><img src="${linkIcon()}" width="12px"/></a>`
|
||||
}</p>
|
||||
${
|
||||
meetingId &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.organizer.language.translate(
|
||||
"meeting_id"
|
||||
)}: <span>${meetingId}</span></div>`
|
||||
}
|
||||
${
|
||||
meetingPassword &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.organizer.language.translate(
|
||||
"meeting_password"
|
||||
)}: <span>${meetingPassword}</span></div>`
|
||||
}
|
||||
${
|
||||
meetingUrl &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}: <a href="${meetingUrl}" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}" style="color: #3E3E3E" target="_blank">${meetingUrl}</a></div>`
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this.calEvent.additionInformation?.hangoutLink) {
|
||||
const hangoutLink: string = this.calEvent.additionInformation.hangoutLink;
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${providerName} ${
|
||||
hangoutLink &&
|
||||
`<a href="${hangoutLink}" target="_blank" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}"><img src="${linkIcon()}" width="12px"></img></a>`
|
||||
}</p>
|
||||
<div style="color: #494949; font-weight: 400; line-height: 24px;"><a href="${hangoutLink}" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}" style="color: #3E3E3E" target="_blank">${hangoutLink}</a></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
providerName || this.calEvent.location
|
||||
}</p>
|
||||
${
|
||||
providerName === "Zoom" || providerName === "Google"
|
||||
? `<p style="color: #494949; font-weight: 400; line-height: 24px;">
|
||||
${this.calEvent.organizer.language.translate("meeting_url_provided_after_confirmed")}
|
||||
</p>`
|
||||
: ``
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getTimezone(): string {
|
||||
return this.calEvent.organizer.timeZone;
|
||||
}
|
||||
|
||||
protected getOrganizerStart(): Dayjs {
|
||||
return dayjs(this.calEvent.startTime).tz(this.getTimezone());
|
||||
}
|
||||
|
||||
protected getOrganizerEnd(): Dayjs {
|
||||
return dayjs(this.calEvent.endTime).tz(this.getTimezone());
|
||||
}
|
||||
|
||||
protected getReason(): string {
|
||||
return `
|
||||
<div style="line-height: 6px; margin-bottom: 24px;">
|
||||
<p style="color: #494949;">${this.calEvent.attendees[0].language.translate("reschedule_reason")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.cancellationReason}</p>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
import { TFunction } from "next-i18next";
|
||||
|
||||
import BaseEmail from "@lib/emails/templates/_base-email";
|
||||
|
||||
import { emailBodyLogo, emailHead, linkIcon } from "./common";
|
||||
|
||||
export type TeamInvite = {
|
||||
language: TFunction;
|
||||
from: string;
|
||||
to: string;
|
||||
teamName: string;
|
||||
joinLink: string;
|
||||
};
|
||||
|
||||
export default class TeamInviteEmail extends BaseEmail {
|
||||
teamInviteEvent: TeamInvite;
|
||||
|
||||
constructor(teamInviteEvent: TeamInvite) {
|
||||
super();
|
||||
this.name = "SEND_TEAM_INVITE_EMAIL";
|
||||
this.teamInviteEvent = teamInviteEvent;
|
||||
}
|
||||
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
return {
|
||||
to: this.teamInviteEvent.to,
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
subject: this.teamInviteEvent.language("user_invited_you", {
|
||||
user: this.teamInviteEvent.from,
|
||||
team: this.teamInviteEvent.teamName,
|
||||
}),
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.teamInviteEvent.language("user_invited_you", {
|
||||
user: this.teamInviteEvent.from,
|
||||
team: this.teamInviteEvent.teamName,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:0px;padding-top:40px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;border-top:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:30px 20px 0 20px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
<div style="line-height: 6px;">
|
||||
<p>${this.teamInviteEvent.language("user_invited_you", {
|
||||
user: this.teamInviteEvent.from,
|
||||
team: this.teamInviteEvent.teamName,
|
||||
})}!</p>
|
||||
<p style="font-weight: 400; line-height: 24px;">${this.teamInviteEvent.language(
|
||||
"calcom_explained"
|
||||
)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
|
||||
<tr>
|
||||
<td align="center" bgcolor="#292929" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#292929;" valign="middle">
|
||||
<p style="display:inline-block;background:#292929;color:#292929;font-family:Roboto, Helvetica, sans-serif;font-size:13px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;">
|
||||
<a href="${
|
||||
this.teamInviteEvent.joinLink
|
||||
}" target="_blank" style="color: #FFFFFF; text-decoration: none">${this.teamInviteEvent.language(
|
||||
"accept_invitation"
|
||||
)} <img src="${linkIcon()}" width="12px"></img></a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
<div style="line-height: 6px;">
|
||||
<p style="font-weight: 400; line-height: 24px;">${this.teamInviteEvent.language(
|
||||
"have_any_questions"
|
||||
)} <a href="mailto:support@cal.com" style="color: #3E3E3E" target="_blank">${this.teamInviteEvent.language(
|
||||
"contact_our_support_team"
|
||||
)}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ const withTM = require("next-transpile-modules")([
|
||||
"@calcom/prisma",
|
||||
"@calcom/stripe",
|
||||
"@calcom/ui",
|
||||
"@calcom/emails",
|
||||
"@calcom/embed-core",
|
||||
"@calcom/embed-snippet",
|
||||
]);
|
||||
|
||||
@@ -159,7 +159,7 @@ if (isSAMLLoginEnabled) {
|
||||
}
|
||||
|
||||
if (true) {
|
||||
const emailsDir = path.resolve(process.cwd(), "lib", "emails", "templates");
|
||||
const emailsDir = path.resolve(process.cwd(), "..", "..", "packages/emails", "templates");
|
||||
providers.push(
|
||||
EmailProvider({
|
||||
maxAge: 10 * 60 * 60, // Magic links are valid for 10 min only
|
||||
|
||||
@@ -2,8 +2,9 @@ import { ResetPasswordRequest } from "@prisma/client";
|
||||
import dayjs from "dayjs";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { sendPasswordResetEmail } from "@lib/emails/email-manager";
|
||||
import { PasswordReset, PASSWORD_RESET_EXPIRY_HOURS } from "@lib/emails/templates/forgot-password-email";
|
||||
import { sendPasswordResetEmail } from "@calcom/emails";
|
||||
import { PASSWORD_RESET_EXPIRY_HOURS } from "@calcom/emails/templates/forgot-password-email";
|
||||
|
||||
import prisma from "@lib/prisma";
|
||||
|
||||
import { getTranslation } from "@server/lib/i18n";
|
||||
@@ -56,13 +57,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
|
||||
const resetLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/auth/forgot-password/${passwordRequest.id}`;
|
||||
const passwordEmail: PasswordReset = {
|
||||
await sendPasswordResetEmail({
|
||||
language: t,
|
||||
user: maybeUser,
|
||||
resetLink,
|
||||
};
|
||||
|
||||
await sendPasswordResetEmail(passwordEmail);
|
||||
});
|
||||
|
||||
/** So we can test the password reset flow on CI */
|
||||
if (process.env.NEXT_PUBLIC_IS_E2E) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { NextApiRequest } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import EventManager from "@calcom/core/EventManager";
|
||||
import { sendDeclinedEmails, sendScheduledEmails } from "@calcom/emails";
|
||||
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import prisma from "@calcom/prisma";
|
||||
@@ -11,7 +12,6 @@ import { refund } from "@ee/lib/stripe/server";
|
||||
|
||||
import { getSession } from "@lib/auth";
|
||||
import { HttpError } from "@lib/core/http/error";
|
||||
import { sendDeclinedEmails, sendScheduledEmails } from "@lib/emails/email-manager";
|
||||
|
||||
import { getTranslation } from "@server/lib/i18n";
|
||||
|
||||
|
||||
@@ -11,6 +11,12 @@ import short from "short-uuid";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
|
||||
import EventManager from "@calcom/core/EventManager";
|
||||
import {
|
||||
sendAttendeeRequestEmail,
|
||||
sendOrganizerRequestEmail,
|
||||
sendRescheduledEmails,
|
||||
sendScheduledEmails,
|
||||
} from "@calcom/emails";
|
||||
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||
import { getDefaultEvent, getGroupName, getUsernameList } from "@calcom/lib/defaultEvents";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
@@ -20,12 +26,6 @@ import type { AdditionInformation, CalendarEvent, RecurringEvent } from "@calcom
|
||||
import type { EventResult, PartialReference } from "@calcom/types/EventManager";
|
||||
import { handlePayment } from "@ee/lib/stripe/server";
|
||||
|
||||
import {
|
||||
sendAttendeeRequestEmail,
|
||||
sendOrganizerRequestEmail,
|
||||
sendRescheduledEmails,
|
||||
sendScheduledEmails,
|
||||
} from "@lib/emails/email-manager";
|
||||
import { ensureArray } from "@lib/ensureArray";
|
||||
import { getEventName } from "@lib/event";
|
||||
import getBusyTimes from "@lib/getBusyTimes";
|
||||
|
||||
@@ -9,10 +9,10 @@ import { getCalendar } from "@calcom/core/CalendarManager";
|
||||
import { CalendarEventBuilder } from "@calcom/core/builders/CalendarEvent/builder";
|
||||
import { CalendarEventDirector } from "@calcom/core/builders/CalendarEvent/director";
|
||||
import { deleteMeeting } from "@calcom/core/videoClient";
|
||||
import { sendRequestRescheduleEmail } from "@calcom/emails";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { sendRequestRescheduleEmail } from "@lib/emails/email-manager";
|
||||
import prisma from "@lib/prisma";
|
||||
|
||||
export type RescheduleResponse = Booking & {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter";
|
||||
import { getCalendar } from "@calcom/core/CalendarManager";
|
||||
import { deleteMeeting } from "@calcom/core/videoClient";
|
||||
import { sendCancelledEmails } from "@calcom/emails";
|
||||
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
@@ -13,7 +14,6 @@ import { refund } from "@ee/lib/stripe/server";
|
||||
|
||||
import { asStringOrNull } from "@lib/asStringOrNull";
|
||||
import { getSession } from "@lib/auth";
|
||||
import { sendCancelledEmails } from "@lib/emails/email-manager";
|
||||
import sendPayload from "@lib/webhooks/sendPayload";
|
||||
import getWebhooks from "@lib/webhooks/subscriptions";
|
||||
|
||||
|
||||
@@ -2,12 +2,11 @@ import { BookingStatus, ReminderType } from "@prisma/client";
|
||||
import dayjs from "dayjs";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { sendOrganizerRequestReminderEmail } from "@calcom/emails";
|
||||
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { sendOrganizerRequestReminderEmail } from "@lib/emails/email-manager";
|
||||
|
||||
import { getTranslation } from "@server/lib/i18n";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { renderEmail } from "@calcom/emails";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (process.env.NODE_ENV !== "development") return res.write("Only for development purposes"), res.end();
|
||||
const t = await getTranslation("en", "common");
|
||||
|
||||
const evt = {
|
||||
type: "30min",
|
||||
title: "30min between Pro Example and pro@example.com",
|
||||
description: null,
|
||||
additionalNotes: "asdasdas",
|
||||
customInputs: {
|
||||
"Custom input 01": "sadasdasdsadasd",
|
||||
"Custom input 02": "asdasdasd",
|
||||
},
|
||||
startTime: "2022-06-03T09:00:00-06:00",
|
||||
endTime: "2022-06-03T09:30:00-06:00",
|
||||
organizer: {
|
||||
name: "Pro Example",
|
||||
email: "pro@example.com",
|
||||
timeZone: "Europe/London",
|
||||
language: { translate: t, locale: "en" },
|
||||
},
|
||||
attendees: [
|
||||
{
|
||||
email: "pro@example.com",
|
||||
name: "pro@example.com",
|
||||
timeZone: "America/Chihuahua",
|
||||
language: { translate: t, locale: "en" },
|
||||
},
|
||||
],
|
||||
location: "Zoom video",
|
||||
destinationCalendar: null,
|
||||
hideCalendarNotes: false,
|
||||
uid: "bwPWLpjYrx4rZf6MCZdKgE",
|
||||
metadata: {},
|
||||
cancellationReason: "It got late",
|
||||
paymentInfo: { id: "pi_12312", link: "https://cal.com", reason: "no reason" },
|
||||
};
|
||||
|
||||
req.statusCode = 200;
|
||||
|
||||
res.setHeader("Content-Type", "text/html");
|
||||
res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate");
|
||||
res.write(
|
||||
renderEmail("OrganizerRequestReminderEmail", {
|
||||
attendee: evt.attendees[0],
|
||||
calEvent: evt,
|
||||
recurringEvent: {},
|
||||
})
|
||||
);
|
||||
res.end();
|
||||
};
|
||||
|
||||
export default handler;
|
||||
@@ -2,10 +2,11 @@ import { MembershipRole } from "@prisma/client";
|
||||
import { randomBytes } from "crypto";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { sendTeamInviteEmail } from "@calcom/emails";
|
||||
import { TeamInvite } from "@calcom/emails/templates/team-invite-email";
|
||||
|
||||
import { getSession } from "@lib/auth";
|
||||
import { BASE_URL } from "@lib/config/constants";
|
||||
import { sendTeamInviteEmail } from "@lib/emails/email-manager";
|
||||
import { TeamInvite } from "@lib/emails/templates/team-invite-email";
|
||||
import prisma from "@lib/prisma";
|
||||
import slugify from "@lib/slugify";
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ import { z } from "zod";
|
||||
import getApps, { getLocationOptions } from "@calcom/app-store/utils";
|
||||
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
|
||||
import { checkPremiumUsername } from "@calcom/ee/lib/core/checkPremiumUsername";
|
||||
import { sendFeedbackEmail } from "@calcom/emails";
|
||||
import { baseEventTypeSelect, bookingMinimalSelect } from "@calcom/prisma";
|
||||
import { RecurringEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { checkRegularUsername } from "@lib/core/checkRegularUsername";
|
||||
import { sendFeedbackEmail } from "@lib/emails/email-manager";
|
||||
import jackson from "@lib/jackson";
|
||||
import prisma from "@lib/prisma";
|
||||
import { isTeamOwner } from "@lib/queries/teams";
|
||||
|
||||
@@ -3,12 +3,11 @@ import dayjs from "dayjs";
|
||||
import { z } from "zod";
|
||||
|
||||
import EventManager from "@calcom/core/EventManager";
|
||||
import { sendLocationChangeEmails } from "@calcom/emails";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import type { AdditionInformation, CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { sendLocationChangeEmails } from "@lib/emails/email-manager";
|
||||
|
||||
import { createProtectedRouter } from "@server/createRouter";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import { MembershipRole, UserPlan } from "@prisma/client";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { MembershipRole, Prisma, UserPlan } from "@prisma/client";
|
||||
import { randomBytes } from "crypto";
|
||||
import { z } from "zod";
|
||||
|
||||
import { sendTeamInviteEmail } from "@calcom/emails";
|
||||
import {
|
||||
addSeat,
|
||||
removeSeat,
|
||||
getTeamSeatStats,
|
||||
downgradeTeamMembers,
|
||||
upgradeTeam,
|
||||
ensureSubscriptionQuantityCorrectness,
|
||||
getTeamSeatStats,
|
||||
removeSeat,
|
||||
upgradeTeam,
|
||||
} from "@calcom/stripe/team-billing";
|
||||
|
||||
import { BASE_URL } from "@lib/config/constants";
|
||||
import { HOSTED_CAL_FEATURES } from "@lib/config/constants";
|
||||
import { sendTeamInviteEmail } from "@lib/emails/email-manager";
|
||||
import { TeamInvite } from "@lib/emails/templates/team-invite-email";
|
||||
import { BASE_URL, HOSTED_CAL_FEATURES } from "@lib/config/constants";
|
||||
import { getUserAvailability } from "@lib/queries/availability";
|
||||
import { getTeamWithMembers, isTeamAdmin, isTeamOwner } from "@lib/queries/teams";
|
||||
import slugify from "@lib/slugify";
|
||||
@@ -262,14 +259,13 @@ export const viewerTeamsRouter = createProtectedRouter()
|
||||
});
|
||||
|
||||
if (ctx?.user?.name && team?.name) {
|
||||
const teamInviteEvent: TeamInvite = {
|
||||
await sendTeamInviteEmail({
|
||||
language: translation,
|
||||
from: ctx.user.name,
|
||||
to: input.usernameOrEmail,
|
||||
teamName: team.name,
|
||||
joinLink: `${BASE_URL}/auth/signup?token=${token}&callbackUrl=${BASE_URL + "/settings/teams"}`,
|
||||
};
|
||||
await sendTeamInviteEmail(teamInviteEvent);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// create provisional membership
|
||||
@@ -294,15 +290,13 @@ export const viewerTeamsRouter = createProtectedRouter()
|
||||
|
||||
// inform user of membership by email
|
||||
if (input.sendEmailInvitation && ctx?.user?.name && team?.name) {
|
||||
const teamInviteEvent: TeamInvite = {
|
||||
await sendTeamInviteEmail({
|
||||
language: translation,
|
||||
from: ctx.user.name,
|
||||
to: input.usernameOrEmail,
|
||||
teamName: team.name,
|
||||
joinLink: BASE_URL + "/settings/teams",
|
||||
};
|
||||
|
||||
await sendTeamInviteEmail(teamInviteEvent);
|
||||
});
|
||||
}
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import AttendeeRequestRescheduledEmail from "./templates/attendee-request-reschedule-email";
|
||||
import OrganizerRequestRescheduledEmail from "./templates/organizer-request-reschedule-email";
|
||||
|
||||
export const sendRequestRescheduleEmail = async (
|
||||
calEvent: CalendarEvent,
|
||||
metadata: { rescheduleLink: string }
|
||||
) => {
|
||||
const emailsToSend: Promise<unknown>[] = [];
|
||||
|
||||
emailsToSend.push(
|
||||
new Promise((resolve, reject) => {
|
||||
try {
|
||||
const requestRescheduleEmail = new AttendeeRequestRescheduledEmail(calEvent, metadata);
|
||||
resolve(requestRescheduleEmail.sendEmail());
|
||||
} catch (e) {
|
||||
reject(console.error("AttendeeRequestRescheduledEmail.sendEmail failed", e));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
emailsToSend.push(
|
||||
new Promise((resolve, reject) => {
|
||||
try {
|
||||
const requestRescheduleEmail = new OrganizerRequestRescheduledEmail(calEvent, metadata);
|
||||
resolve(requestRescheduleEmail.sendEmail());
|
||||
} catch (e) {
|
||||
reject(console.error("OrganizerRequestRescheduledEmail.sendEmail failed", e));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(emailsToSend);
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
import SendmailTransport from "nodemailer/lib/sendmail-transport";
|
||||
import SMTPConnection from "nodemailer/lib/smtp-connection";
|
||||
|
||||
function detectTransport(): SendmailTransport.Options | SMTPConnection.Options | string {
|
||||
if (process.env.EMAIL_SERVER) {
|
||||
return process.env.EMAIL_SERVER;
|
||||
}
|
||||
|
||||
if (process.env.EMAIL_SERVER_HOST) {
|
||||
const port = parseInt(process.env.EMAIL_SERVER_PORT!);
|
||||
const transport = {
|
||||
host: process.env.EMAIL_SERVER_HOST,
|
||||
port,
|
||||
auth: {
|
||||
user: process.env.EMAIL_SERVER_USER,
|
||||
pass: process.env.EMAIL_SERVER_PASSWORD,
|
||||
},
|
||||
secure: port === 465,
|
||||
};
|
||||
|
||||
return transport;
|
||||
}
|
||||
|
||||
return {
|
||||
sendmail: true,
|
||||
newline: "unix",
|
||||
path: "/usr/sbin/sendmail",
|
||||
};
|
||||
}
|
||||
|
||||
export const serverConfig = {
|
||||
transport: detectTransport(),
|
||||
from: process.env.EMAIL_FROM,
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BookingStatus, User, Booking, BookingReference } from "@prisma/client";
|
||||
import { Booking, BookingReference, BookingStatus, User } from "@prisma/client";
|
||||
import dayjs from "dayjs";
|
||||
import type { TFunction } from "next-i18next";
|
||||
|
||||
@@ -6,13 +6,13 @@ import EventManager from "@calcom/core/EventManager";
|
||||
import { CalendarEventBuilder } from "@calcom/core/builders/CalendarEvent/builder";
|
||||
import { CalendarEventDirector } from "@calcom/core/builders/CalendarEvent/director";
|
||||
import { deleteMeeting } from "@calcom/core/videoClient";
|
||||
import { sendRequestRescheduleEmail } from "@calcom/emails";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { getCalendar } from "../../_utils/getCalendar";
|
||||
import { sendRequestRescheduleEmail } from "./emailManager";
|
||||
|
||||
type PersonAttendeeCommonFields = Pick<User, "id" | "email" | "name" | "locale" | "timeZone" | "username">;
|
||||
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { createEvent, DateArray, Person } from "ics";
|
||||
|
||||
import { getCancelLink } from "@calcom/lib/CalEventParser";
|
||||
import { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import BaseTemplate from "./base-template";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class AttendeeRequestRescheduledEmail extends BaseTemplate {
|
||||
private metadata: { rescheduleLink: string };
|
||||
constructor(calEvent: CalendarEvent, metadata: { rescheduleLink: string }) {
|
||||
super(calEvent);
|
||||
this.metadata = metadata;
|
||||
}
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.attendees[0].email];
|
||||
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("requested_to_reschedule_subject_attendee", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
// @OVERRIDE
|
||||
protected getiCalEventAsString(): string | undefined {
|
||||
const icsEvent = createEvent({
|
||||
start: dayjs(this.calEvent.startTime)
|
||||
.utc()
|
||||
.toArray()
|
||||
.slice(0, 6)
|
||||
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
|
||||
startInputType: "utc",
|
||||
productId: "calendso/ics",
|
||||
title: this.calEvent.organizer.language.translate("ics_event_title", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
description: this.getTextBody(),
|
||||
duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") },
|
||||
organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email },
|
||||
attendees: this.calEvent.attendees.map((attendee: Person) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
})),
|
||||
status: "CANCELLED",
|
||||
method: "CANCEL",
|
||||
});
|
||||
if (icsEvent.error) {
|
||||
throw icsEvent.error;
|
||||
}
|
||||
return icsEvent.value;
|
||||
}
|
||||
// @OVERRIDE
|
||||
protected getWhen(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("when")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;text-decoration: line-through;">
|
||||
${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format(
|
||||
"YYYY"
|
||||
)} | ${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_title_attendee")}
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_subtitle", {
|
||||
organizer: this.calEvent.organizer.name,
|
||||
})},
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("request_reschedule_title_attendee"),
|
||||
this.calEvent.organizer.language.translate("request_reschedule_subtitle", {
|
||||
organizer: this.calEvent.organizer.name,
|
||||
})
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 40px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getAdditionalNotes()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;text-align:center;color:#3E3E3E;">
|
||||
<a style="padding: 8px 16px;background-color: #292929;color: white;border-radius: 2px;display: inline-block;margin-bottom: 16px;"
|
||||
href="${this.metadata.rescheduleLink}" target="_blank"
|
||||
>
|
||||
Book a new time
|
||||
<img src="https://app.cal.com/emails/linkIcon.png" style="width:16px; margin-left: 5px;filter: brightness(0) invert(1); vertical-align: top;" />
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,417 +0,0 @@
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { createEvent, DateArray, Person } from "ics";
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
import { getAppName } from "@calcom/app-store/utils";
|
||||
import { getCancelLink, getRichDescription } from "@calcom/lib/CalEventParser";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { serverConfig } from "../emailServerConfig";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
linkIcon,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class OrganizerScheduledEmail {
|
||||
calEvent: CalendarEvent;
|
||||
|
||||
constructor(calEvent: CalendarEvent) {
|
||||
this.calEvent = calEvent;
|
||||
}
|
||||
|
||||
public sendEmail() {
|
||||
new Promise((resolve, reject) =>
|
||||
nodemailer
|
||||
.createTransport(this.getMailerOptions().transport)
|
||||
.sendMail(this.getNodeMailerPayload(), (_err, info) => {
|
||||
if (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
this.printNodeMailerError(err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(info);
|
||||
}
|
||||
})
|
||||
).catch((e) => console.error("sendEmail", e));
|
||||
return new Promise((resolve) => resolve("send mail async"));
|
||||
}
|
||||
|
||||
protected getiCalEventAsString(): string | undefined {
|
||||
const icsEvent = createEvent({
|
||||
start: dayjs(this.calEvent.startTime)
|
||||
.utc()
|
||||
.toArray()
|
||||
.slice(0, 6)
|
||||
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
|
||||
startInputType: "utc",
|
||||
productId: "calendso/ics",
|
||||
title: this.calEvent.organizer.language.translate("ics_event_title", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
description: this.getTextBody(),
|
||||
duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") },
|
||||
organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email },
|
||||
attendees: this.calEvent.attendees.map((attendee: Person) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
})),
|
||||
status: "CONFIRMED",
|
||||
});
|
||||
if (icsEvent.error) {
|
||||
throw icsEvent.error;
|
||||
}
|
||||
return icsEvent.value;
|
||||
}
|
||||
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.organizer.email];
|
||||
if (this.calEvent.team) {
|
||||
this.calEvent.team.members.forEach((member) => {
|
||||
const memberAttendee = this.calEvent.attendees.find((attendee) => attendee.name === member);
|
||||
if (memberAttendee) {
|
||||
toAddresses.push(memberAttendee.email);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("confirmed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getMailerOptions() {
|
||||
return {
|
||||
transport: serverConfig.transport,
|
||||
from: serverConfig.from,
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("new_event_scheduled")}
|
||||
${this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")}
|
||||
|
||||
${getRichDescription(this.calEvent)}
|
||||
`.trim();
|
||||
}
|
||||
|
||||
protected printNodeMailerError(error: Error): void {
|
||||
console.error("SEND_BOOKING_CONFIRMATION_ERROR", this.calEvent.organizer.email, error);
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("confirmed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("checkCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("new_event_scheduled"),
|
||||
this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:0px;text-align:left;color:#3E3E3E;">
|
||||
${this.getManageLink()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getManageLink(): string {
|
||||
const manageText = this.calEvent.organizer.language.translate("manage_this_event");
|
||||
return `<p>${this.calEvent.organizer.language.translate(
|
||||
"need_to_reschedule_or_cancel"
|
||||
)}</p><p style="font-weight: 400; line-height: 24px;"><a href="${getCancelLink(
|
||||
this.calEvent
|
||||
)}" style="color: #3E3E3E;" alt="${manageText}">${manageText}</a></p>`;
|
||||
}
|
||||
|
||||
protected getWhat(): string {
|
||||
return `
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("what")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.type}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getWhen(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("when")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">
|
||||
${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format(
|
||||
"YYYY"
|
||||
)} | ${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getWho(): string {
|
||||
const attendees = this.calEvent.attendees
|
||||
.map((attendee) => {
|
||||
return `<div style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
attendee?.name || `${this.calEvent.organizer.language.translate("guest")}`
|
||||
} <span style="color: #888888"><a href="mailto:${attendee.email}" style="color: #888888;">${
|
||||
attendee.email
|
||||
}</a></span></div>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
const organizer = `<div style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
this.calEvent.organizer.name
|
||||
} - ${this.calEvent.organizer.language.translate(
|
||||
"organizer"
|
||||
)} <span style="color: #888888"><a href="mailto:${
|
||||
this.calEvent.organizer.email
|
||||
}" style="color: #888888;">${this.calEvent.organizer.email}</a></span></div>`;
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("who")}</p>
|
||||
${organizer + attendees}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getAdditionalNotes(): string {
|
||||
if (!this.calEvent.additionalNotes) return "";
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("additional_notes")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px; white-space: pre-wrap;">${
|
||||
this.calEvent.additionalNotes
|
||||
}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getDescription(): string {
|
||||
if (!this.calEvent.description) return "";
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("description")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px; white-space: pre-wrap;">${
|
||||
this.calEvent.description
|
||||
}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getLocation(): string {
|
||||
let providerName = this.calEvent.location ? getAppName(this.calEvent.location) : "";
|
||||
|
||||
if (this.calEvent.location && this.calEvent.location.includes("integrations:")) {
|
||||
const location = this.calEvent.location.split(":")[1];
|
||||
providerName = location[0].toUpperCase() + location.slice(1);
|
||||
}
|
||||
|
||||
// If location its a url, probably we should be validating it with a custom library
|
||||
if (this.calEvent.location && /^https?:\/\//.test(this.calEvent.location)) {
|
||||
providerName = this.calEvent.location;
|
||||
}
|
||||
|
||||
if (this.calEvent.videoCallData) {
|
||||
const meetingId = this.calEvent.videoCallData.id;
|
||||
const meetingPassword = this.calEvent.videoCallData.password;
|
||||
const meetingUrl = this.calEvent.videoCallData.url;
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${providerName} ${
|
||||
meetingUrl &&
|
||||
`<a href="${meetingUrl}" target="_blank" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}"><img src="${linkIcon()}" width="12px"></img></a>`
|
||||
}</p>
|
||||
${
|
||||
meetingId &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.organizer.language.translate(
|
||||
"meeting_id"
|
||||
)}: <span>${meetingId}</span></div>`
|
||||
}
|
||||
${
|
||||
meetingPassword &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.organizer.language.translate(
|
||||
"meeting_password"
|
||||
)}: <span>${meetingPassword}</span></div>`
|
||||
}
|
||||
${
|
||||
meetingUrl &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}: <a href="${meetingUrl}" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}" style="color: #3E3E3E" target="_blank">${meetingUrl}</a></div>`
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this.calEvent.additionInformation?.hangoutLink) {
|
||||
const hangoutLink: string = this.calEvent.additionInformation.hangoutLink;
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${providerName} ${
|
||||
hangoutLink &&
|
||||
`<a href="${hangoutLink}" target="_blank" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}"><img src="${linkIcon()}" width="12px"></img></a>`
|
||||
}</p>
|
||||
<div style="color: #494949; font-weight: 400; line-height: 24px;"><a href="${hangoutLink}" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}" style="color: #3E3E3E" target="_blank">${hangoutLink}</a></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
providerName || this.calEvent.location
|
||||
}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getTimezone(): string {
|
||||
return this.calEvent.organizer.timeZone;
|
||||
}
|
||||
|
||||
protected getOrganizerStart(): Dayjs {
|
||||
return dayjs(this.calEvent.startTime).tz(this.getTimezone());
|
||||
}
|
||||
|
||||
protected getOrganizerEnd(): Dayjs {
|
||||
return dayjs(this.calEvent.endTime).tz(this.getTimezone());
|
||||
}
|
||||
|
||||
protected getReason(): string {
|
||||
return `
|
||||
<div style="line-height: 6px; margin-bottom: 24px;">
|
||||
<p style="color: #494949;">${this.calEvent.attendees[0].language.translate("reschedule_reason")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.cancellationReason}</p>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { IS_PRODUCTION, BASE_URL } from "@lib/config/constants";
|
||||
|
||||
export const emailBodyLogo = (): string => {
|
||||
const image = IS_PRODUCTION
|
||||
? BASE_URL + "/emails/CalLogo@2x.png"
|
||||
: "https://app.cal.com/emails/CalLogo@2x.png";
|
||||
|
||||
return `
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-top:32px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:89px;">
|
||||
<a href="${BASE_URL}" target="_blank">
|
||||
<img height="19" src="${image}" style="border:0;display:block;outline:none;text-decoration:none;height:19px;width:100%;font-size:13px;" width="89" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
@@ -1,91 +0,0 @@
|
||||
export const emailHead = (headerContent: string): string => {
|
||||
return `
|
||||
<head>
|
||||
<title>${headerContent}</title>
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<!--<![endif]-->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
#outlook a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table,
|
||||
td {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin: 13px 0;
|
||||
}
|
||||
</style>
|
||||
<!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]-->
|
||||
<!--[if lte mso 11]>
|
||||
<style type="text/css">
|
||||
.mj-outlook-group-fix { width:100% !important; }
|
||||
</style>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700" rel="stylesheet" type="text/css">
|
||||
<style type="text/css">
|
||||
@import url(https://fonts.googleapis.com/css?family=Roboto:400,500,700);
|
||||
</style>
|
||||
<!--<![endif]-->
|
||||
<style type="text/css">
|
||||
@media only screen and (min-width:480px) {
|
||||
.mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style media="screen and (min-width:480px)">
|
||||
.moz-text-html .mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
@media only screen and (max-width:480px) {
|
||||
table.mj-full-width-mobile {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
td.mj-full-width-mobile {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
`;
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
export { emailHead } from "./head";
|
||||
export { emailSchedulingBodyHeader } from "./scheduling-body-head";
|
||||
export { emailBodyLogo } from "./body-logo";
|
||||
export { emailScheduledBodyHeaderContent } from "./scheduling-body-head-content";
|
||||
export { emailSchedulingBodyDivider } from "./scheduling-body-divider";
|
||||
export { linkIcon } from "./link-icon";
|
||||
@@ -1,5 +0,0 @@
|
||||
import { IS_PRODUCTION, BASE_URL } from "@lib/config/constants";
|
||||
|
||||
export const linkIcon = (): string => {
|
||||
return IS_PRODUCTION ? BASE_URL + "/emails/linkIcon.png" : "https://app.cal.com/emails/linkIcon.png";
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
export const emailSchedulingBodyDivider = (): string => {
|
||||
return `
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:15px 0px 0 0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-bottom:15px;word-break:break-word;">
|
||||
<p style="border-top:solid 1px #E1E1E1;font-size:1px;margin:0px auto;width:100%;">
|
||||
</p>
|
||||
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #E1E1E1;font-size:1px;margin:0px auto;width:548px;" role="presentation" width="548px" ><tr><td style="height:0;line-height:0;">
|
||||
</td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
export const emailScheduledBodyHeaderContent = (title: string, subtitle: string): string => {
|
||||
return `
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-top:24px;padding-bottom:0px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:24px;font-weight:700;line-height:24px;text-align:center;color:#292929;">${title}</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:400;line-height:24px;text-align:center;color:#494949;">${subtitle}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
@@ -1,71 +0,0 @@
|
||||
import { IS_PRODUCTION, BASE_URL } from "@lib/config/constants";
|
||||
|
||||
export type BodyHeadType = "checkCircle" | "xCircle" | "calendarCircle";
|
||||
|
||||
export const getHeadImage = (headerType: BodyHeadType): string => {
|
||||
switch (headerType) {
|
||||
case "checkCircle":
|
||||
return IS_PRODUCTION
|
||||
? BASE_URL + "/emails/checkCircle@2x.png"
|
||||
: "https://app.cal.com/emails/checkCircle@2x.png";
|
||||
case "xCircle":
|
||||
return IS_PRODUCTION
|
||||
? BASE_URL + "/emails/xCircle@2x.png"
|
||||
: "https://app.cal.com/emails/xCircle@2x.png";
|
||||
case "calendarCircle":
|
||||
return IS_PRODUCTION
|
||||
? BASE_URL + "/emails/calendarCircle@2x.png"
|
||||
: "https://app.cal.com/emails/calendarCircle@2x.png";
|
||||
}
|
||||
};
|
||||
|
||||
export const emailSchedulingBodyHeader = (headerType: BodyHeadType): string => {
|
||||
const image = getHeadImage(headerType);
|
||||
|
||||
return `
|
||||
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:0px;padding-top:40px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;border-top:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:30px 30px 0 30px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:558px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:64px;">
|
||||
<img height="64" src="${image}" style="border:0;display:block;outline:none;text-decoration:none;height:64px;width:100%;font-size:13px;" width="64" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
@@ -1,190 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { createEvent, DateArray, Person } from "ics";
|
||||
|
||||
import { getCancelLink } from "@calcom/lib/CalEventParser";
|
||||
import { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import BaseTemplate from "./base-template";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class OrganizerRequestRescheduledEmail extends BaseTemplate {
|
||||
private metadata: { rescheduleLink: string };
|
||||
constructor(calEvent: CalendarEvent, metadata: { rescheduleLink: string }) {
|
||||
super(calEvent);
|
||||
this.metadata = metadata;
|
||||
}
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.organizer.email];
|
||||
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
// @OVERRIDE
|
||||
protected getiCalEventAsString(): string | undefined {
|
||||
const icsEvent = createEvent({
|
||||
start: dayjs(this.calEvent.startTime)
|
||||
.utc()
|
||||
.toArray()
|
||||
.slice(0, 6)
|
||||
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
|
||||
startInputType: "utc",
|
||||
productId: "calendso/ics",
|
||||
title: this.calEvent.organizer.language.translate("ics_event_title", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
description: this.getTextBody(),
|
||||
duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") },
|
||||
organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email },
|
||||
attendees: this.calEvent.attendees.map((attendee: Person) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
})),
|
||||
status: "CANCELLED",
|
||||
method: "CANCEL",
|
||||
});
|
||||
if (icsEvent.error) {
|
||||
throw icsEvent.error;
|
||||
}
|
||||
return icsEvent.value;
|
||||
}
|
||||
// @OVERRIDE
|
||||
protected getWhen(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("when")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;text-decoration: line-through;">
|
||||
${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format(
|
||||
"YYYY"
|
||||
)} | ${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_title_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
})}
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_subtitle_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
})},
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("request_reschedule_title_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
this.calEvent.organizer.language.translate("request_reschedule_subtitle_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
})
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 40px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getAdditionalNotes()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import AttendeeRequestRescheduledEmail from "./templates/attendee-request-reschedule-email";
|
||||
import OrganizerRequestRescheduledEmail from "./templates/organizer-request-reschedule-email";
|
||||
|
||||
export const sendRequestRescheduleEmail = async (
|
||||
calEvent: CalendarEvent,
|
||||
metadata: { rescheduleLink: string }
|
||||
) => {
|
||||
const emailsToSend: Promise<unknown>[] = [];
|
||||
|
||||
emailsToSend.push(
|
||||
new Promise((resolve, reject) => {
|
||||
try {
|
||||
const requestRescheduleEmail = new AttendeeRequestRescheduledEmail(calEvent, metadata);
|
||||
resolve(requestRescheduleEmail.sendEmail());
|
||||
} catch (e) {
|
||||
reject(console.error("AttendeeRequestRescheduledEmail.sendEmail failed", e));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
emailsToSend.push(
|
||||
new Promise((resolve, reject) => {
|
||||
try {
|
||||
const requestRescheduleEmail = new OrganizerRequestRescheduledEmail(calEvent, metadata);
|
||||
resolve(requestRescheduleEmail.sendEmail());
|
||||
} catch (e) {
|
||||
reject(console.error("OrganizerRequestRescheduledEmail.sendEmail failed", e));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(emailsToSend);
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
import SendmailTransport from "nodemailer/lib/sendmail-transport";
|
||||
import SMTPConnection from "nodemailer/lib/smtp-connection";
|
||||
|
||||
function detectTransport(): SendmailTransport.Options | SMTPConnection.Options | string {
|
||||
if (process.env.EMAIL_SERVER) {
|
||||
return process.env.EMAIL_SERVER;
|
||||
}
|
||||
|
||||
if (process.env.EMAIL_SERVER_HOST) {
|
||||
const port = parseInt(process.env.EMAIL_SERVER_PORT!);
|
||||
const transport = {
|
||||
host: process.env.EMAIL_SERVER_HOST,
|
||||
port,
|
||||
auth: {
|
||||
user: process.env.EMAIL_SERVER_USER,
|
||||
pass: process.env.EMAIL_SERVER_PASSWORD,
|
||||
},
|
||||
secure: port === 465,
|
||||
};
|
||||
|
||||
return transport;
|
||||
}
|
||||
|
||||
return {
|
||||
sendmail: true,
|
||||
newline: "unix",
|
||||
path: "/usr/sbin/sendmail",
|
||||
};
|
||||
}
|
||||
|
||||
export const serverConfig = {
|
||||
transport: detectTransport(),
|
||||
from: process.env.EMAIL_FROM,
|
||||
};
|
||||
@@ -6,13 +6,13 @@ import EventManager from "@calcom/core/EventManager";
|
||||
import { CalendarEventBuilder } from "@calcom/core/builders/CalendarEvent/builder";
|
||||
import { CalendarEventDirector } from "@calcom/core/builders/CalendarEvent/director";
|
||||
import { deleteMeeting } from "@calcom/core/videoClient";
|
||||
import { sendRequestRescheduleEmail } from "@calcom/emails";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { getCalendar } from "../../_utils/getCalendar";
|
||||
import { sendRequestRescheduleEmail } from "./emailManager";
|
||||
|
||||
type PersonAttendeeCommonFields = Pick<User, "id" | "email" | "name" | "locale" | "timeZone" | "username">;
|
||||
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { createEvent, DateArray, Person } from "ics";
|
||||
|
||||
import { getCancelLink } from "@calcom/lib/CalEventParser";
|
||||
import { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import BaseTemplate from "./base-template";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class AttendeeRequestRescheduledEmail extends BaseTemplate {
|
||||
private metadata: { rescheduleLink: string };
|
||||
constructor(calEvent: CalendarEvent, metadata: { rescheduleLink: string }) {
|
||||
super(calEvent);
|
||||
this.metadata = metadata;
|
||||
}
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.attendees[0].email];
|
||||
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("requested_to_reschedule_subject_attendee", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
// @OVERRIDE
|
||||
protected getiCalEventAsString(): string | undefined {
|
||||
const icsEvent = createEvent({
|
||||
start: dayjs(this.calEvent.startTime)
|
||||
.utc()
|
||||
.toArray()
|
||||
.slice(0, 6)
|
||||
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
|
||||
startInputType: "utc",
|
||||
productId: "calendso/ics",
|
||||
title: this.calEvent.organizer.language.translate("ics_event_title", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
description: this.getTextBody(),
|
||||
duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") },
|
||||
organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email },
|
||||
attendees: this.calEvent.attendees.map((attendee: Person) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
})),
|
||||
status: "CANCELLED",
|
||||
method: "CANCEL",
|
||||
});
|
||||
if (icsEvent.error) {
|
||||
throw icsEvent.error;
|
||||
}
|
||||
return icsEvent.value;
|
||||
}
|
||||
// @OVERRIDE
|
||||
protected getWhen(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("when")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;text-decoration: line-through;">
|
||||
${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format(
|
||||
"YYYY"
|
||||
)} | ${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_title_attendee")}
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_subtitle", {
|
||||
organizer: this.calEvent.organizer.name,
|
||||
})},
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("request_reschedule_title_attendee"),
|
||||
this.calEvent.organizer.language.translate("request_reschedule_subtitle", {
|
||||
organizer: this.calEvent.organizer.name,
|
||||
})
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 40px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getAdditionalNotes()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;text-align:center;color:#3E3E3E;">
|
||||
<a style="padding: 8px 16px;background-color: #292929;color: white;border-radius: 2px;display: inline-block;margin-bottom: 16px;"
|
||||
href="${this.metadata.rescheduleLink}" target="_blank"
|
||||
>
|
||||
Book a new time
|
||||
<img src="https://app.cal.com/emails/linkIcon.png" style="width:16px; margin-left: 5px;filter: brightness(0) invert(1); vertical-align: top;" />
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,417 +0,0 @@
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { createEvent, DateArray, Person } from "ics";
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
import { getAppName } from "@calcom/app-store/utils";
|
||||
import { getCancelLink, getRichDescription } from "@calcom/lib/CalEventParser";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { serverConfig } from "../emailServerConfig";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
linkIcon,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class OrganizerScheduledEmail {
|
||||
calEvent: CalendarEvent;
|
||||
|
||||
constructor(calEvent: CalendarEvent) {
|
||||
this.calEvent = calEvent;
|
||||
}
|
||||
|
||||
public sendEmail() {
|
||||
new Promise((resolve, reject) =>
|
||||
nodemailer
|
||||
.createTransport(this.getMailerOptions().transport)
|
||||
.sendMail(this.getNodeMailerPayload(), (_err, info) => {
|
||||
if (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
this.printNodeMailerError(err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(info);
|
||||
}
|
||||
})
|
||||
).catch((e) => console.error("sendEmail", e));
|
||||
return new Promise((resolve) => resolve("send mail async"));
|
||||
}
|
||||
|
||||
protected getiCalEventAsString(): string | undefined {
|
||||
const icsEvent = createEvent({
|
||||
start: dayjs(this.calEvent.startTime)
|
||||
.utc()
|
||||
.toArray()
|
||||
.slice(0, 6)
|
||||
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
|
||||
startInputType: "utc",
|
||||
productId: "calendso/ics",
|
||||
title: this.calEvent.organizer.language.translate("ics_event_title", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
description: this.getTextBody(),
|
||||
duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") },
|
||||
organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email },
|
||||
attendees: this.calEvent.attendees.map((attendee: Person) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
})),
|
||||
status: "CONFIRMED",
|
||||
});
|
||||
if (icsEvent.error) {
|
||||
throw icsEvent.error;
|
||||
}
|
||||
return icsEvent.value;
|
||||
}
|
||||
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.organizer.email];
|
||||
if (this.calEvent.team) {
|
||||
this.calEvent.team.members.forEach((member) => {
|
||||
const memberAttendee = this.calEvent.attendees.find((attendee) => attendee.name === member);
|
||||
if (memberAttendee) {
|
||||
toAddresses.push(memberAttendee.email);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("confirmed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
protected getMailerOptions() {
|
||||
return {
|
||||
transport: serverConfig.transport,
|
||||
from: serverConfig.from,
|
||||
};
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("new_event_scheduled")}
|
||||
${this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")}
|
||||
|
||||
${getRichDescription(this.calEvent)}
|
||||
`.trim();
|
||||
}
|
||||
|
||||
protected printNodeMailerError(error: Error): void {
|
||||
console.error("SEND_BOOKING_CONFIRMATION_ERROR", this.calEvent.organizer.email, error);
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("confirmed_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("checkCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("new_event_scheduled"),
|
||||
this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getDescription()}
|
||||
${this.getAdditionalNotes()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-bottom:1px solid #E1E1E1;border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:0px;text-align:left;color:#3E3E3E;">
|
||||
${this.getManageLink()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getManageLink(): string {
|
||||
const manageText = this.calEvent.organizer.language.translate("manage_this_event");
|
||||
return `<p>${this.calEvent.organizer.language.translate(
|
||||
"need_to_reschedule_or_cancel"
|
||||
)}</p><p style="font-weight: 400; line-height: 24px;"><a href="${getCancelLink(
|
||||
this.calEvent
|
||||
)}" style="color: #3E3E3E;" alt="${manageText}">${manageText}</a></p>`;
|
||||
}
|
||||
|
||||
protected getWhat(): string {
|
||||
return `
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("what")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.type}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getWhen(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("when")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">
|
||||
${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format(
|
||||
"YYYY"
|
||||
)} | ${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getWho(): string {
|
||||
const attendees = this.calEvent.attendees
|
||||
.map((attendee) => {
|
||||
return `<div style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
attendee?.name || `${this.calEvent.organizer.language.translate("guest")}`
|
||||
} <span style="color: #888888"><a href="mailto:${attendee.email}" style="color: #888888;">${
|
||||
attendee.email
|
||||
}</a></span></div>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
const organizer = `<div style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
this.calEvent.organizer.name
|
||||
} - ${this.calEvent.organizer.language.translate(
|
||||
"organizer"
|
||||
)} <span style="color: #888888"><a href="mailto:${
|
||||
this.calEvent.organizer.email
|
||||
}" style="color: #888888;">${this.calEvent.organizer.email}</a></span></div>`;
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("who")}</p>
|
||||
${organizer + attendees}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getAdditionalNotes(): string {
|
||||
if (!this.calEvent.additionalNotes) return "";
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("additional_notes")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px; white-space: pre-wrap;">${
|
||||
this.calEvent.additionalNotes
|
||||
}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getDescription(): string {
|
||||
if (!this.calEvent.description) return "";
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("description")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px; white-space: pre-wrap;">${
|
||||
this.calEvent.description
|
||||
}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getLocation(): string {
|
||||
let providerName = this.calEvent.location && getAppName(this.calEvent.location);
|
||||
|
||||
if (this.calEvent.location && this.calEvent.location.includes("integrations:")) {
|
||||
const location = this.calEvent.location.split(":")[1];
|
||||
providerName = location[0].toUpperCase() + location.slice(1);
|
||||
}
|
||||
|
||||
// If location its a url, probably we should be validating it with a custom library
|
||||
if (this.calEvent.location && /^https?:\/\//.test(this.calEvent.location)) {
|
||||
providerName = this.calEvent.location;
|
||||
}
|
||||
|
||||
if (this.calEvent.videoCallData) {
|
||||
const meetingId = this.calEvent.videoCallData.id;
|
||||
const meetingPassword = this.calEvent.videoCallData.password;
|
||||
const meetingUrl = this.calEvent.videoCallData.url;
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${providerName} ${
|
||||
meetingUrl &&
|
||||
`<a href="${meetingUrl}" target="_blank" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}"><img src="${linkIcon()}" width="12px"></img></a>`
|
||||
}</p>
|
||||
${
|
||||
meetingId &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.organizer.language.translate(
|
||||
"meeting_id"
|
||||
)}: <span>${meetingId}</span></div>`
|
||||
}
|
||||
${
|
||||
meetingPassword &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.organizer.language.translate(
|
||||
"meeting_password"
|
||||
)}: <span>${meetingPassword}</span></div>`
|
||||
}
|
||||
${
|
||||
meetingUrl &&
|
||||
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}: <a href="${meetingUrl}" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}" style="color: #3E3E3E" target="_blank">${meetingUrl}</a></div>`
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this.calEvent.additionInformation?.hangoutLink) {
|
||||
const hangoutLink: string = this.calEvent.additionInformation.hangoutLink;
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${providerName} ${
|
||||
hangoutLink &&
|
||||
`<a href="${hangoutLink}" target="_blank" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}"><img src="${linkIcon()}" width="12px"></img></a>`
|
||||
}</p>
|
||||
<div style="color: #494949; font-weight: 400; line-height: 24px;"><a href="${hangoutLink}" alt="${this.calEvent.organizer.language.translate(
|
||||
"meeting_url"
|
||||
)}" style="color: #3E3E3E" target="_blank">${hangoutLink}</a></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("where")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||
providerName || this.calEvent.location
|
||||
}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getTimezone(): string {
|
||||
return this.calEvent.organizer.timeZone;
|
||||
}
|
||||
|
||||
protected getOrganizerStart(): Dayjs {
|
||||
return dayjs(this.calEvent.startTime).tz(this.getTimezone());
|
||||
}
|
||||
|
||||
protected getOrganizerEnd(): Dayjs {
|
||||
return dayjs(this.calEvent.endTime).tz(this.getTimezone());
|
||||
}
|
||||
|
||||
protected getReason(): string {
|
||||
return `
|
||||
<div style="line-height: 6px; margin-bottom: 24px;">
|
||||
<p style="color: #494949;">${this.calEvent.attendees[0].language.translate("reschedule_reason")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.cancellationReason}</p>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
export const emailHead = (headerContent: string): string => {
|
||||
return `
|
||||
<head>
|
||||
<title>${headerContent}</title>
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<!--<![endif]-->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
#outlook a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table,
|
||||
td {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin: 13px 0;
|
||||
}
|
||||
</style>
|
||||
<!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]-->
|
||||
<!--[if lte mso 11]>
|
||||
<style type="text/css">
|
||||
.mj-outlook-group-fix { width:100% !important; }
|
||||
</style>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700" rel="stylesheet" type="text/css">
|
||||
<style type="text/css">
|
||||
@import url(https://fonts.googleapis.com/css?family=Roboto:400,500,700);
|
||||
</style>
|
||||
<!--<![endif]-->
|
||||
<style type="text/css">
|
||||
@media only screen and (min-width:480px) {
|
||||
.mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style media="screen and (min-width:480px)">
|
||||
.moz-text-html .mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
@media only screen and (max-width:480px) {
|
||||
table.mj-full-width-mobile {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
td.mj-full-width-mobile {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
`;
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
export { emailHead } from "./head";
|
||||
export { emailSchedulingBodyHeader } from "./scheduling-body-head";
|
||||
export { emailBodyLogo } from "./body-logo";
|
||||
export { emailScheduledBodyHeaderContent } from "./scheduling-body-head-content";
|
||||
export { emailSchedulingBodyDivider } from "./scheduling-body-divider";
|
||||
export { linkIcon } from "./link-icon";
|
||||
@@ -1,5 +0,0 @@
|
||||
import { IS_PRODUCTION, BASE_URL } from "@lib/config/constants";
|
||||
|
||||
export const linkIcon = (): string => {
|
||||
return IS_PRODUCTION ? BASE_URL + "/emails/linkIcon.png" : "https://app.cal.com/emails/linkIcon.png";
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
export const emailSchedulingBodyDivider = (): string => {
|
||||
return `
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:15px 0px 0 0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-bottom:15px;word-break:break-word;">
|
||||
<p style="border-top:solid 1px #E1E1E1;font-size:1px;margin:0px auto;width:100%;">
|
||||
</p>
|
||||
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #E1E1E1;font-size:1px;margin:0px auto;width:548px;" role="presentation" width="548px" ><tr><td style="height:0;line-height:0;">
|
||||
</td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
export const emailScheduledBodyHeaderContent = (title: string, subtitle: string): string => {
|
||||
return `
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-top:24px;padding-bottom:0px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:24px;font-weight:700;line-height:24px;text-align:center;color:#292929;">${title}</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:400;line-height:24px;text-align:center;color:#494949;">${subtitle}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
-190
@@ -1,190 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { createEvent, DateArray, Person } from "ics";
|
||||
|
||||
import { getCancelLink } from "@calcom/lib/CalEventParser";
|
||||
import { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import BaseTemplate from "./base-template";
|
||||
import {
|
||||
emailHead,
|
||||
emailSchedulingBodyHeader,
|
||||
emailBodyLogo,
|
||||
emailScheduledBodyHeaderContent,
|
||||
emailSchedulingBodyDivider,
|
||||
} from "./common";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export default class OrganizerRequestRescheduledEmail extends BaseTemplate {
|
||||
private metadata: { rescheduleLink: string };
|
||||
constructor(calEvent: CalendarEvent, metadata: { rescheduleLink: string }) {
|
||||
super(calEvent);
|
||||
this.metadata = metadata;
|
||||
}
|
||||
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||
const toAddresses = [this.calEvent.organizer.email];
|
||||
|
||||
return {
|
||||
icalEvent: {
|
||||
filename: "event.ics",
|
||||
content: this.getiCalEventAsString(),
|
||||
},
|
||||
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.calEvent.organizer.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
})}`,
|
||||
html: this.getHtmlBody(),
|
||||
text: this.getTextBody(),
|
||||
};
|
||||
}
|
||||
|
||||
// @OVERRIDE
|
||||
protected getiCalEventAsString(): string | undefined {
|
||||
const icsEvent = createEvent({
|
||||
start: dayjs(this.calEvent.startTime)
|
||||
.utc()
|
||||
.toArray()
|
||||
.slice(0, 6)
|
||||
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
|
||||
startInputType: "utc",
|
||||
productId: "calendso/ics",
|
||||
title: this.calEvent.organizer.language.translate("ics_event_title", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
description: this.getTextBody(),
|
||||
duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") },
|
||||
organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email },
|
||||
attendees: this.calEvent.attendees.map((attendee: Person) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
})),
|
||||
status: "CANCELLED",
|
||||
method: "CANCEL",
|
||||
});
|
||||
if (icsEvent.error) {
|
||||
throw icsEvent.error;
|
||||
}
|
||||
return icsEvent.value;
|
||||
}
|
||||
// @OVERRIDE
|
||||
protected getWhen(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.organizer.language.translate("when")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;text-decoration: line-through;">
|
||||
${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format(
|
||||
"YYYY"
|
||||
)} | ${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected getTextBody(): string {
|
||||
return `
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_title_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
})}
|
||||
${this.calEvent.organizer.language.translate("request_reschedule_subtitle_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
})},
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
|
||||
${getCancelLink(this.calEvent)}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
protected getHtmlBody(): string {
|
||||
const headerContent = this.calEvent.organizer.language.translate("rescheduled_event_type_subject", {
|
||||
eventType: this.calEvent.type,
|
||||
name: this.calEvent.attendees[0].name,
|
||||
date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format(
|
||||
"h:mma"
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("dddd").toLowerCase()
|
||||
)}, ${this.calEvent.organizer.language.translate(
|
||||
this.getOrganizerStart().format("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`,
|
||||
});
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
${emailHead(headerContent)}
|
||||
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||
<div style="background-color:#F5F5F5;">
|
||||
${emailSchedulingBodyHeader("calendarCircle")}
|
||||
${emailScheduledBodyHeaderContent(
|
||||
this.calEvent.organizer.language.translate("request_reschedule_title_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
}),
|
||||
this.calEvent.organizer.language.translate("request_reschedule_subtitle_organizer", {
|
||||
attendee: this.calEvent.attendees[0].name,
|
||||
})
|
||||
)}
|
||||
${emailSchedulingBodyDivider()}
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 40px;word-break:break-word;">
|
||||
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||
${this.calEvent.cancellationReason && this.getReason()}
|
||||
${this.getWhat()}
|
||||
${this.getWhen()}
|
||||
${this.getWho()}
|
||||
${this.getAdditionalNotes()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${emailBodyLogo()}
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# JSX email templates
|
||||
|
||||
- `components` Holds reusable patterns
|
||||
- `templates` A template equals a type of email sent
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { renderEmail } from "@calcom/emails";
|
||||
|
||||
renderEmail("TeamInviteEmail", */{
|
||||
language: t,
|
||||
from: "teampro@example.com",
|
||||
to: "pro@example.com",
|
||||
teamName: "Team Pro",
|
||||
joinLink: "https://cal.com",
|
||||
});
|
||||
```
|
||||
|
||||
The first argument is the template name as defined inside `templates/index.ts`. The second argument are the template props.
|
||||
|
||||
## Development
|
||||
|
||||
You can use an API endpoint to preview the email HTML, there's already one on `/apps/web/pages/api/email.ts` feel free to change the template to the one you're currently working on.
|
||||
@@ -1,24 +1,24 @@
|
||||
import type { CalendarEvent, Person, RecurringEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import AttendeeAwaitingPaymentEmail from "@lib/emails/templates/attendee-awaiting-payment-email";
|
||||
import AttendeeCancelledEmail from "@lib/emails/templates/attendee-cancelled-email";
|
||||
import AttendeeDeclinedEmail from "@lib/emails/templates/attendee-declined-email";
|
||||
import AttendeeLocationChangeEmail from "@lib/emails/templates/attendee-location-change-email";
|
||||
import AttendeeRequestEmail from "@lib/emails/templates/attendee-request-email";
|
||||
import AttendeeRequestRescheduledEmail from "@lib/emails/templates/attendee-request-reschedule-email";
|
||||
import AttendeeRescheduledEmail from "@lib/emails/templates/attendee-rescheduled-email";
|
||||
import AttendeeScheduledEmail from "@lib/emails/templates/attendee-scheduled-email";
|
||||
import FeedbackEmail, { Feedback } from "@lib/emails/templates/feedback-email";
|
||||
import ForgotPasswordEmail, { PasswordReset } from "@lib/emails/templates/forgot-password-email";
|
||||
import OrganizerCancelledEmail from "@lib/emails/templates/organizer-cancelled-email";
|
||||
import OrganizerLocationChangeEmail from "@lib/emails/templates/organizer-location-change-email";
|
||||
import OrganizerPaymentRefundFailedEmail from "@lib/emails/templates/organizer-payment-refund-failed-email";
|
||||
import OrganizerRequestEmail from "@lib/emails/templates/organizer-request-email";
|
||||
import OrganizerRequestReminderEmail from "@lib/emails/templates/organizer-request-reminder-email";
|
||||
import OrganizerRequestRescheduleEmail from "@lib/emails/templates/organizer-request-reschedule-email";
|
||||
import OrganizerRescheduledEmail from "@lib/emails/templates/organizer-rescheduled-email";
|
||||
import OrganizerScheduledEmail from "@lib/emails/templates/organizer-scheduled-email";
|
||||
import TeamInviteEmail, { TeamInvite } from "@lib/emails/templates/team-invite-email";
|
||||
import AttendeeAwaitingPaymentEmail from "./templates/attendee-awaiting-payment-email";
|
||||
import AttendeeCancelledEmail from "./templates/attendee-cancelled-email";
|
||||
import AttendeeDeclinedEmail from "./templates/attendee-declined-email";
|
||||
import AttendeeLocationChangeEmail from "./templates/attendee-location-change-email";
|
||||
import AttendeeRequestEmail from "./templates/attendee-request-email";
|
||||
import AttendeeRequestRescheduledEmail from "./templates/attendee-request-reschedule-email";
|
||||
import AttendeeRescheduledEmail from "./templates/attendee-rescheduled-email";
|
||||
import AttendeeScheduledEmail from "./templates/attendee-scheduled-email";
|
||||
import FeedbackEmail, { Feedback } from "./templates/feedback-email";
|
||||
import ForgotPasswordEmail, { PasswordReset } from "./templates/forgot-password-email";
|
||||
import OrganizerCancelledEmail from "./templates/organizer-cancelled-email";
|
||||
import OrganizerLocationChangeEmail from "./templates/organizer-location-change-email";
|
||||
import OrganizerPaymentRefundFailedEmail from "./templates/organizer-payment-refund-failed-email";
|
||||
import OrganizerRequestEmail from "./templates/organizer-request-email";
|
||||
import OrganizerRequestReminderEmail from "./templates/organizer-request-reminder-email";
|
||||
import OrganizerRequestRescheduleEmail from "./templates/organizer-request-reschedule-email";
|
||||
import OrganizerRescheduledEmail from "./templates/organizer-rescheduled-email";
|
||||
import OrganizerScheduledEmail from "./templates/organizer-scheduled-email";
|
||||
import TeamInviteEmail, { TeamInvite } from "./templates/team-invite-email";
|
||||
|
||||
export const sendScheduledEmails = async (calEvent: CalendarEvent, recurringEvent: RecurringEvent = {}) => {
|
||||
const emailsToSend: Promise<unknown>[] = [];
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./email-manager";
|
||||
export { default as renderEmail } from "./src/renderEmail";
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "@calcom/emails",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"@calcom/lib": "*",
|
||||
"dayjs": "^1.11.2",
|
||||
"next-i18next": "^11.0.0",
|
||||
"rrule": "^2.6.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/* eslint-disable @next/next/no-head-element */
|
||||
import BaseTable from "./BaseTable";
|
||||
import EmailBodyLogo from "./EmailBodyLogo";
|
||||
import EmailHead from "./EmailHead";
|
||||
import EmailScheduledBodyHeaderContent from "./EmailScheduledBodyHeaderContent";
|
||||
import EmailSchedulingBodyDivider from "./EmailSchedulingBodyDivider";
|
||||
import EmailSchedulingBodyHeader, { BodyHeadType } from "./EmailSchedulingBodyHeader";
|
||||
import RawHtml from "./RawHtml";
|
||||
import Row from "./Row";
|
||||
|
||||
const Html = (props: { children: React.ReactNode }) => (
|
||||
<>
|
||||
<RawHtml html={`<!doctype html>`} />
|
||||
<html>{props.children}</html>
|
||||
</>
|
||||
);
|
||||
|
||||
export const BaseEmailHtml = (props: {
|
||||
children: React.ReactNode;
|
||||
callToAction?: React.ReactNode;
|
||||
subject: string;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
headerType?: BodyHeadType;
|
||||
}) => {
|
||||
return (
|
||||
<Html>
|
||||
<EmailHead title={props.subject} />
|
||||
<body style={{ wordSpacing: "normal", backgroundColor: "#F5F5F5" }}>
|
||||
<div style={{ backgroundColor: "#F5F5F5" }}>
|
||||
<RawHtml
|
||||
html={`<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->`}
|
||||
/>
|
||||
<div style={{ margin: "0px auto", maxWidth: 600 }}>
|
||||
<Row align="center" border="0" style={{ width: "100%" }}>
|
||||
<td
|
||||
style={{
|
||||
direction: "ltr",
|
||||
fontSize: "0px",
|
||||
padding: "0px",
|
||||
paddingTop: "40px",
|
||||
textAlign: "center",
|
||||
}}>
|
||||
<RawHtml
|
||||
html={`<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->`}
|
||||
/>
|
||||
</td>
|
||||
</Row>
|
||||
</div>
|
||||
{props.headerType && <EmailSchedulingBodyHeader headerType={props.headerType} />}
|
||||
{props.title && <EmailScheduledBodyHeaderContent title={props.title} subtitle={props.subtitle} />}
|
||||
{(props.headerType || props.title || props.subtitle) && <EmailSchedulingBodyDivider />}
|
||||
|
||||
<RawHtml
|
||||
html={`<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" className="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->`}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
background: "#FFFFFF",
|
||||
backgroundColor: "#FFFFFF",
|
||||
margin: "0px auto",
|
||||
maxWidth: 600,
|
||||
}}>
|
||||
<Row
|
||||
align="center"
|
||||
border="0"
|
||||
style={{ background: "#FFFFFF", backgroundColor: "#FFFFFF", width: "100%" }}>
|
||||
<td
|
||||
style={{
|
||||
borderLeft: "1px solid #E1E1E1",
|
||||
borderRight: "1px solid #E1E1E1",
|
||||
direction: "ltr",
|
||||
fontSize: 0,
|
||||
padding: 0,
|
||||
textAlign: "center",
|
||||
}}>
|
||||
<RawHtml
|
||||
html={`<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td className="" style="vertical-align:top;width:598px;" ><![endif]-->`}
|
||||
/>
|
||||
<div
|
||||
className="mj-column-per-100 mj-outlook-group-fix"
|
||||
style={{
|
||||
fontSize: 0,
|
||||
textAlign: "left",
|
||||
direction: "ltr",
|
||||
display: "inline-block",
|
||||
verticalAlign: "top",
|
||||
width: "100%",
|
||||
}}>
|
||||
<Row border="0" style={{ verticalAlign: "top" }} width="100%">
|
||||
<td align="left" style={{ fontSize: 0, padding: "10px 25px", wordBreak: "break-word" }}>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: "Roboto, Helvetica, sans-serif",
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
lineHeight: 1,
|
||||
textAlign: "left",
|
||||
color: "#3E3E3E",
|
||||
}}>
|
||||
{props.children}
|
||||
</div>
|
||||
</td>
|
||||
</Row>
|
||||
</div>
|
||||
<RawHtml html={`<!--[if mso | IE]></td></tr></table><![endif]-->`} />
|
||||
</td>
|
||||
</Row>
|
||||
</div>
|
||||
{props.callToAction && <EmailSchedulingBodyDivider />}
|
||||
<RawHtml
|
||||
html={`<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" className="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->`}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
background: "#FFFFFF",
|
||||
backgroundColor: "#FFFFFF",
|
||||
margin: "0px auto",
|
||||
maxWidth: 600,
|
||||
}}>
|
||||
<Row
|
||||
align="center"
|
||||
border="0"
|
||||
style={{ background: "#FFFFFF", backgroundColor: "#FFFFFF", width: "100%" }}>
|
||||
<td
|
||||
style={{
|
||||
borderBottom: "1px solid #E1E1E1",
|
||||
borderLeft: "1px solid #E1E1E1",
|
||||
borderRight: "1px solid #E1E1E1",
|
||||
direction: "ltr",
|
||||
fontSize: 0,
|
||||
padding: 0,
|
||||
textAlign: "center",
|
||||
}}>
|
||||
<RawHtml
|
||||
html={`<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td className="" style="vertical-align:top;width:598px;" ><![endif]-->`}
|
||||
/>
|
||||
{props.callToAction && (
|
||||
<div
|
||||
className="mj-column-per-100 mj-outlook-group-fix"
|
||||
style={{
|
||||
fontSize: 0,
|
||||
textAlign: "left",
|
||||
direction: "ltr",
|
||||
display: "inline-block",
|
||||
verticalAlign: "top",
|
||||
width: "100%",
|
||||
}}>
|
||||
<BaseTable border="0" style={{ verticalAlign: "top" }} width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
vertical-align="middle"
|
||||
style={{ fontSize: 0, padding: "10px 25px", wordBreak: "break-word" }}>
|
||||
{props.callToAction}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
align="left"
|
||||
style={{ fontSize: 0, padding: "10px 25px", wordBreak: "break-word" }}>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: "Roboto, Helvetica, sans-serif",
|
||||
fontSize: 13,
|
||||
lineHeight: 1,
|
||||
textAlign: "left",
|
||||
color: "#000000",
|
||||
}}></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</BaseTable>
|
||||
</div>
|
||||
)}
|
||||
<RawHtml html={`<!--[if mso | IE]></td></tr></table><![endif]-->`} />
|
||||
</td>
|
||||
</Row>
|
||||
</div>
|
||||
<EmailBodyLogo />
|
||||
<RawHtml html={`<!--[if mso | IE]></td></tr></table><![endif]-->`} />
|
||||
</div>
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
type BaseTableProps = React.DetailedHTMLProps<React.TableHTMLAttributes<HTMLTableElement>, HTMLTableElement> &
|
||||
Partial<Pick<HTMLTableElement, "align" | "border">>;
|
||||
|
||||
const BaseTable = ({ children, ...rest }: BaseTableProps) => (
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
<table cellPadding="0" cellSpacing="0" role="presentation" {...rest}>
|
||||
{children}
|
||||
</table>
|
||||
);
|
||||
|
||||
export default BaseTable;
|
||||
@@ -0,0 +1,30 @@
|
||||
import { LinkIcon } from "./LinkIcon";
|
||||
|
||||
export const CallToAction = (props: { label: string; href: string }) => (
|
||||
<p
|
||||
style={{
|
||||
display: "inline-block",
|
||||
background: "#292929",
|
||||
color: "#ffffff",
|
||||
fontFamily: "Roboto, Helvetica, sans-serif",
|
||||
fontSize: "16px",
|
||||
fontWeight: 500,
|
||||
lineHeight: "120%",
|
||||
margin: 0,
|
||||
textDecoration: "none",
|
||||
textTransform: "none",
|
||||
padding: "10px 25px",
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
msoPaddingAlt: "0px",
|
||||
borderRadius: "3px",
|
||||
}}>
|
||||
<a
|
||||
style={{ color: "#FFFFFF", textDecoration: "none" }}
|
||||
href={props.href}
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
{props.label} <LinkIcon />
|
||||
</a>
|
||||
</p>
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
export const CallToActionTable = (props: { children: React.ReactNode }) => (
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
bgcolor="#292929"
|
||||
role="presentation"
|
||||
style={{
|
||||
border: "none",
|
||||
borderRadius: "3px",
|
||||
cursor: "auto",
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
msoPaddingAlt: "10px 25px",
|
||||
background: "#292929",
|
||||
}}
|
||||
valign="middle">
|
||||
{props.children}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { Info } from "./Info";
|
||||
|
||||
export function CustomInputs(props: { calEvent: CalendarEvent }) {
|
||||
const { customInputs } = props.calEvent;
|
||||
if (!customInputs) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.keys(customInputs).map((key) =>
|
||||
customInputs[key] !== "" ? (
|
||||
<Info key={key} label={key} description={`${customInputs[key]}`} withSpacer />
|
||||
) : null
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { BASE_URL, IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
|
||||
import RawHtml from "./RawHtml";
|
||||
import Row from "./Row";
|
||||
|
||||
const CommentIE = ({ html = "" }) => <RawHtml html={`<!--[if mso | IE]>${html}<![endif]-->`} />;
|
||||
|
||||
const EmailBodyLogo = () => {
|
||||
const image = IS_PRODUCTION
|
||||
? BASE_URL + "/emails/CalLogo@2x.png"
|
||||
: "https://app.cal.com/emails/CalLogo@2x.png";
|
||||
|
||||
return (
|
||||
<>
|
||||
<CommentIE
|
||||
html={`</td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"></td>`}
|
||||
/>
|
||||
<div style={{ margin: "0px auto", maxWidth: 600 }}>
|
||||
<Row align="center" border="0" style={{ width: "100%" }}>
|
||||
<td
|
||||
style={{
|
||||
direction: "ltr",
|
||||
fontSize: "0px",
|
||||
padding: "0px",
|
||||
textAlign: "center",
|
||||
}}>
|
||||
<CommentIE
|
||||
html={`<table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td style="vertical-align:top;width:600px;" >`}
|
||||
/>
|
||||
<div
|
||||
className="mj-column-per-100 mj-outlook-group-fix"
|
||||
style={{
|
||||
fontSize: "0px",
|
||||
textAlign: "left",
|
||||
direction: "ltr",
|
||||
display: "inline-block",
|
||||
verticalAlign: "top",
|
||||
width: "100%",
|
||||
}}>
|
||||
<Row border="0" style={{ verticalAlign: "top" }} width="100%">
|
||||
<td
|
||||
align="center"
|
||||
style={{
|
||||
fontSize: "0px",
|
||||
padding: "10px 25px",
|
||||
paddingTop: "32px",
|
||||
wordBreak: "break-word",
|
||||
}}>
|
||||
<Row border="0" style={{ borderCollapse: "collapse", borderSpacing: "0px" }}>
|
||||
<td style={{ width: "89px" }}>
|
||||
<a href={BASE_URL} target="_blank" rel="noreferrer">
|
||||
<img
|
||||
height="19"
|
||||
src={image}
|
||||
style={{
|
||||
border: "0",
|
||||
display: "block",
|
||||
outline: "none",
|
||||
textDecoration: "none",
|
||||
height: "19px",
|
||||
width: "100%",
|
||||
fontSize: "13px",
|
||||
}}
|
||||
width="89"
|
||||
alt=""
|
||||
/>
|
||||
</a>
|
||||
</td>
|
||||
</Row>
|
||||
</td>
|
||||
</Row>
|
||||
</div>
|
||||
<CommentIE html={`</td></tr></table>`} />
|
||||
</td>
|
||||
</Row>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmailBodyLogo;
|
||||
@@ -0,0 +1,71 @@
|
||||
import RawHtml from "./RawHtml";
|
||||
import Row from "./Row";
|
||||
|
||||
const EmailCommonDivider = ({
|
||||
children,
|
||||
mutipleRows = false,
|
||||
headStyles,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
mutipleRows?: boolean;
|
||||
headStyles?: React.DetailedHTMLProps<
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>,
|
||||
HTMLTableCellElement
|
||||
>["style"];
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<RawHtml
|
||||
html={`<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->`}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
background: "#FFFFFF",
|
||||
backgroundColor: "#FFFFFF",
|
||||
margin: "0px auto",
|
||||
maxWidth: 600,
|
||||
}}>
|
||||
<Row
|
||||
align="center"
|
||||
border="0"
|
||||
style={{
|
||||
background: "#FFFFFF",
|
||||
backgroundColor: "#FFFFFF",
|
||||
width: "100%",
|
||||
}}>
|
||||
<td
|
||||
style={{
|
||||
borderLeft: "1px solid #E1E1E1",
|
||||
borderRight: "1px solid #E1E1E1",
|
||||
direction: "ltr",
|
||||
fontSize: 0,
|
||||
padding: "15px 0px 0 0px",
|
||||
textAlign: "center",
|
||||
...headStyles,
|
||||
}}>
|
||||
<RawHtml
|
||||
html={`<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->`}
|
||||
/>
|
||||
<div
|
||||
className="mj-column-per-100 mj-outlook-group-fix"
|
||||
style={{
|
||||
fontSize: 0,
|
||||
textAlign: "left",
|
||||
direction: "ltr",
|
||||
display: "inline-block",
|
||||
verticalAlign: "top",
|
||||
width: "100%",
|
||||
}}>
|
||||
<Row border="0" style={{ verticalAlign: "top" }} width="100%" multiple={mutipleRows}>
|
||||
{children}
|
||||
</Row>
|
||||
</div>
|
||||
<RawHtml html={`<!--[if mso | IE]></td></tr></table><![endif]-->`} />
|
||||
</td>
|
||||
</Row>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmailCommonDivider;
|
||||
@@ -0,0 +1,93 @@
|
||||
/* eslint-disable @next/next/no-head-element */
|
||||
import RawHtml from "./RawHtml";
|
||||
|
||||
const EmailHead = ({ title = "" }) => {
|
||||
return (
|
||||
<head>
|
||||
<title>{title}</title>
|
||||
<RawHtml
|
||||
html={`<!--[if !mso]><!--><meta http-equiv="X-UA-Compatible" content="IE=edge"><!--<![endif]-->`}
|
||||
/>
|
||||
<meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type="text/css">
|
||||
{`
|
||||
#outlook a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table,
|
||||
td {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin: 13px 0;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<RawHtml
|
||||
html={`<!--[if mso]><noscript><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml></noscript><![endif]-->`}
|
||||
/>
|
||||
<RawHtml
|
||||
html={`<!--[if lte mso 11]><style type="text/css">.mj-outlook-group-fix { width:100% !important; }</style><![endif]-->`}
|
||||
/>
|
||||
<RawHtml
|
||||
html={`<!--[if !mso]><!--><link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700" rel="stylesheet" type="text/css"/>
|
||||
<style type="text/css">@import url(https://fonts.googleapis.com/css?family=Roboto:400,500,700);</style><!--<![endif]-->`}
|
||||
/>
|
||||
<style type="text/css">
|
||||
{`
|
||||
@media only screen and (min-width: 480px) {
|
||||
.mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<style media="screen and (min-width:480px)">
|
||||
{`
|
||||
.moz-text-html .mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
{`
|
||||
@media only screen and (max-width: 480px) {
|
||||
table.mj-full-width-mobile {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
td.mj-full-width-mobile {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</head>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmailHead;
|
||||
@@ -0,0 +1,48 @@
|
||||
import EmailCommonDivider from "./EmailCommonDivider";
|
||||
|
||||
const EmailScheduledBodyHeaderContent = (props: { title: string; subtitle?: string }) => (
|
||||
<EmailCommonDivider headStyles={{ padding: 0 }} mutipleRows>
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style={{
|
||||
fontSize: 0,
|
||||
padding: "10px 25px",
|
||||
paddingTop: 24,
|
||||
paddingBottom: 0,
|
||||
wordBreak: "break-word",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: "Roboto, Helvetica, sans-serif",
|
||||
fontSize: 24,
|
||||
fontWeight: 700,
|
||||
lineHeight: "24px",
|
||||
textAlign: "center",
|
||||
color: "#292929",
|
||||
}}>
|
||||
{props.title}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{props.subtitle && (
|
||||
<tr>
|
||||
<td align="center" style={{ fontSize: 0, padding: "10px 25px", wordBreak: "break-word" }}>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: "Roboto, Helvetica, sans-serif",
|
||||
fontSize: 16,
|
||||
fontWeight: 400,
|
||||
lineHeight: "24px",
|
||||
textAlign: "center",
|
||||
color: "#494949",
|
||||
}}>
|
||||
{props.subtitle}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</EmailCommonDivider>
|
||||
);
|
||||
|
||||
export default EmailScheduledBodyHeaderContent;
|
||||
@@ -0,0 +1,28 @@
|
||||
import EmailCommonDivider from "./EmailCommonDivider";
|
||||
import RawHtml from "./RawHtml";
|
||||
|
||||
export const EmailSchedulingBodyDivider = () => (
|
||||
<EmailCommonDivider>
|
||||
<td
|
||||
align="center"
|
||||
style={{
|
||||
fontSize: 0,
|
||||
padding: "10px 25px",
|
||||
paddingBottom: 15,
|
||||
wordBreak: "break-word",
|
||||
}}>
|
||||
<p
|
||||
style={{
|
||||
borderTop: "solid 1px #E1E1E1",
|
||||
fontSize: 1,
|
||||
margin: "0px auto",
|
||||
width: "100%",
|
||||
}}></p>
|
||||
<RawHtml
|
||||
html={`<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #E1E1E1;font-size:1px;margin:0px auto;width:548px;" role="presentation" width="548px" ><tr><td style="height:0;line-height:0;"> </td></tr></table><![endif]-->`}
|
||||
/>
|
||||
</td>
|
||||
</EmailCommonDivider>
|
||||
);
|
||||
|
||||
export default EmailSchedulingBodyDivider;
|
||||
@@ -0,0 +1,63 @@
|
||||
import { BASE_URL, IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
|
||||
import EmailCommonDivider from "./EmailCommonDivider";
|
||||
import Row from "./Row";
|
||||
|
||||
export type BodyHeadType = "checkCircle" | "xCircle" | "calendarCircle";
|
||||
|
||||
export const getHeadImage = (headerType: BodyHeadType): string => {
|
||||
switch (headerType) {
|
||||
case "checkCircle":
|
||||
return IS_PRODUCTION
|
||||
? BASE_URL + "/emails/checkCircle@2x.png"
|
||||
: "https://app.cal.com/emails/checkCircle@2x.png";
|
||||
case "xCircle":
|
||||
return IS_PRODUCTION
|
||||
? BASE_URL + "/emails/xCircle@2x.png"
|
||||
: "https://app.cal.com/emails/xCircle@2x.png";
|
||||
case "calendarCircle":
|
||||
return IS_PRODUCTION
|
||||
? BASE_URL + "/emails/calendarCircle@2x.png"
|
||||
: "https://app.cal.com/emails/calendarCircle@2x.png";
|
||||
}
|
||||
};
|
||||
|
||||
const EmailSchedulingBodyHeader = (props: { headerType: BodyHeadType }) => {
|
||||
const image = getHeadImage(props.headerType);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EmailCommonDivider headStyles={{ padding: "30px 30px 0 30px", borderTop: "1px solid #E1E1E1" }}>
|
||||
<td
|
||||
align="center"
|
||||
style={{
|
||||
fontSize: "0px",
|
||||
padding: "10px 25px",
|
||||
wordBreak: "break-word",
|
||||
}}>
|
||||
<Row border="0" role="presentation" style={{ borderCollapse: "collapse", borderSpacing: "0px" }}>
|
||||
<td style={{ width: 64 }}>
|
||||
<img
|
||||
height="64"
|
||||
src={image}
|
||||
style={{
|
||||
border: "0",
|
||||
display: "block",
|
||||
outline: "none",
|
||||
textDecoration: "none",
|
||||
height: "64px",
|
||||
width: "100%",
|
||||
fontSize: "13px",
|
||||
}}
|
||||
width="64"
|
||||
alt=""
|
||||
/>
|
||||
</td>
|
||||
</Row>
|
||||
</td>
|
||||
</EmailCommonDivider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmailSchedulingBodyHeader;
|
||||
@@ -0,0 +1,30 @@
|
||||
const Spacer = () => <p style={{ height: 6 }} />;
|
||||
|
||||
export const Info = (props: {
|
||||
label: string;
|
||||
description: React.ReactNode | undefined | null;
|
||||
extraInfo?: React.ReactNode;
|
||||
withSpacer?: boolean;
|
||||
lineThrough?: boolean;
|
||||
}) => {
|
||||
if (!props.description) return null;
|
||||
return (
|
||||
<>
|
||||
{props.withSpacer && <Spacer />}
|
||||
<div style={{ lineHeight: "6px" }}>
|
||||
<p style={{ color: "#494949" }}>{props.label}</p>
|
||||
<p
|
||||
style={{
|
||||
color: "#494949",
|
||||
fontWeight: 400,
|
||||
lineHeight: "24px",
|
||||
whiteSpace: "pre-wrap",
|
||||
textDecoration: props.lineThrough ? "line-through" : undefined,
|
||||
}}>
|
||||
{props.description}
|
||||
</p>
|
||||
{props.extraInfo}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import { BASE_URL, IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
|
||||
export const LinkIcon = () => (
|
||||
<img
|
||||
src={IS_PRODUCTION ? BASE_URL + "/emails/linkIcon.png" : "https://app.cal.com/emails/linkIcon.png"}
|
||||
width="12px"
|
||||
alt=""
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,124 @@
|
||||
import { getAppName } from "@calcom/app-store/utils";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { Info } from "./Info";
|
||||
import { LinkIcon } from "./LinkIcon";
|
||||
|
||||
export function LocationInfo(props: { calEvent: CalendarEvent }) {
|
||||
const t = props.calEvent.attendees[0].language.translate;
|
||||
let providerName = props.calEvent.location && getAppName(props.calEvent.location);
|
||||
|
||||
if (props.calEvent.location && props.calEvent.location.includes("integrations:")) {
|
||||
const location = props.calEvent.location.split(":")[1];
|
||||
providerName = location[0].toUpperCase() + location.slice(1);
|
||||
}
|
||||
|
||||
// If location its a url, probably we should be validating it with a custom library
|
||||
if (props.calEvent.location && /^https?:\/\//.test(props.calEvent.location)) {
|
||||
providerName = props.calEvent.location;
|
||||
}
|
||||
|
||||
if (props.calEvent.videoCallData) {
|
||||
const meetingId = props.calEvent.videoCallData.id;
|
||||
const meetingPassword = props.calEvent.videoCallData.password;
|
||||
const meetingUrl = props.calEvent.videoCallData.url;
|
||||
|
||||
return (
|
||||
<Info
|
||||
label={t("where")}
|
||||
withSpacer
|
||||
description={
|
||||
<>
|
||||
{providerName}
|
||||
{meetingUrl && (
|
||||
<a
|
||||
href={meetingUrl}
|
||||
target="_blank"
|
||||
title={t("meeting_url")}
|
||||
style={{ color: "#3E3E3E" }}
|
||||
rel="noreferrer">
|
||||
<LinkIcon />
|
||||
</a>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
extraInfo={
|
||||
<>
|
||||
{meetingId && (
|
||||
<div style={{ color: "#494949", fontWeight: 400, lineHeight: "24px" }}>
|
||||
<>
|
||||
{t("meeting_id")}: <span>{meetingId}</span>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
{meetingPassword && (
|
||||
<div style={{ color: "#494949", fontWeight: 400, lineHeight: "24px" }}>
|
||||
<>
|
||||
{t("meeting_password")}: <span>{meetingPassword}</span>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
{meetingUrl && (
|
||||
<div style={{ color: "#494949", fontWeight: 400, lineHeight: "24px" }}>
|
||||
<>
|
||||
{t("meeting_url")}:{" "}
|
||||
<a href="${meetingUrl}" title={t("meeting_url")} style={{ color: "#3E3E3E" }}>
|
||||
{meetingUrl}
|
||||
</a>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.calEvent.additionInformation?.hangoutLink) {
|
||||
const hangoutLink: string = props.calEvent.additionInformation.hangoutLink;
|
||||
|
||||
return (
|
||||
<Info
|
||||
label={t("where")}
|
||||
withSpacer
|
||||
description={
|
||||
<>
|
||||
{providerName}
|
||||
{hangoutLink && (
|
||||
<a
|
||||
href={hangoutLink}
|
||||
target="_blank"
|
||||
title={t("meeting_url")}
|
||||
style={{ color: "#3E3E3E" }}
|
||||
rel="noreferrer">
|
||||
<LinkIcon />
|
||||
</a>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
extraInfo={
|
||||
providerName === "Zoom" || providerName === "Google" ? (
|
||||
<p style={{ color: "#494949", fontWeight: 400, lineHeight: "24px" }}>
|
||||
<>{t("meeting_url_provided_after_confirmed")}</>
|
||||
</p>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Info
|
||||
label={t("where")}
|
||||
withSpacer
|
||||
description={providerName || props.calEvent.location}
|
||||
extraInfo={
|
||||
providerName === "Zoom" || providerName === "Google" ? (
|
||||
<p style={{ color: "#494949", fontWeight: 400, lineHeight: "24px" }}>
|
||||
<>{t("meeting_url_provided_after_confirmed")}</>
|
||||
</p>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { getCancelLink } from "@calcom/lib/CalEventParser";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
export function ManageLink(props: { calEvent: CalendarEvent; attendee: Person }) {
|
||||
// Only the original attendee can make changes to the event
|
||||
// Guests cannot
|
||||
if (props.attendee.email !== props.calEvent.attendees[0].email) return null;
|
||||
const t = props.attendee.language.translate;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
fontFamily: "Roboto, Helvetica, sans-serif",
|
||||
fontSize: "16px",
|
||||
fontWeight: 500,
|
||||
lineHeight: "0px",
|
||||
textAlign: "left",
|
||||
color: "#3e3e3e",
|
||||
}}>
|
||||
<p>
|
||||
<>{t("need_to_reschedule_or_cancel")}</>
|
||||
</p>
|
||||
<p style={{ fontWeight: 400, lineHeight: "24px" }}>
|
||||
<a href={getCancelLink(props.calEvent)} style={{ color: "#3e3e3e" }}>
|
||||
<>{t("manage_this_event")}</>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const RawHtml = ({ html = "" }) => (
|
||||
<script dangerouslySetInnerHTML={{ __html: `</script>${html}<script>` }} />
|
||||
);
|
||||
|
||||
export default RawHtml;
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { ComponentProps } from "react";
|
||||
|
||||
import BaseTable from "./BaseTable";
|
||||
|
||||
const Row = ({
|
||||
children,
|
||||
multiple = false,
|
||||
...rest
|
||||
}: { children: React.ReactNode; multiple?: boolean } & ComponentProps<typeof BaseTable>) => (
|
||||
<BaseTable {...rest}>
|
||||
<tbody>{multiple ? children : <tr>{children}</tr>}</tbody>
|
||||
</BaseTable>
|
||||
);
|
||||
|
||||
export default Row;
|
||||
@@ -0,0 +1,57 @@
|
||||
import dayjs from "dayjs";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import { TFunction } from "next-i18next";
|
||||
import rrule from "rrule";
|
||||
|
||||
import type { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { Info } from "./Info";
|
||||
|
||||
dayjs.extend(timezone);
|
||||
|
||||
function getRecurringWhen(props: { calEvent: CalendarEvent; recurringEvent: RecurringEvent }) {
|
||||
const t = props.calEvent.attendees[0].language.translate;
|
||||
return props.recurringEvent?.count && props.recurringEvent?.freq
|
||||
? ` - ${t("every_for_freq", {
|
||||
freq: t(`${rrule.FREQUENCIES[props.recurringEvent.freq].toString().toLowerCase()}`),
|
||||
})} ${props.recurringEvent.count} ${t(
|
||||
`${rrule.FREQUENCIES[props.recurringEvent.freq].toString().toLowerCase()}`,
|
||||
{ count: props.recurringEvent.count }
|
||||
)}`
|
||||
: "";
|
||||
}
|
||||
|
||||
export function WhenInfo(props: {
|
||||
calEvent: CalendarEvent;
|
||||
recurringEvent: RecurringEvent;
|
||||
timeZone: string;
|
||||
t: TFunction;
|
||||
}) {
|
||||
const { timeZone, t } = props;
|
||||
|
||||
function getRecipientStart(format: string) {
|
||||
return dayjs(props.calEvent.startTime).tz(timeZone).format(format);
|
||||
}
|
||||
|
||||
function getRecipientEnd(format: string) {
|
||||
return dayjs(props.calEvent.endTime).tz(timeZone).format(format);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Info
|
||||
label={`${t("when")} ${getRecurringWhen(props)}`}
|
||||
lineThrough={!!props.calEvent.cancellationReason}
|
||||
description={
|
||||
<>
|
||||
{props.recurringEvent?.count ? `${t("starting")} ` : ""}
|
||||
{t(getRecipientStart("dddd").toLowerCase())}, {t(getRecipientStart("MMMM").toLowerCase())}{" "}
|
||||
{getRecipientStart("D, YYYY | h:mma")} - {getRecipientEnd("h:mma")}{" "}
|
||||
<span style={{ color: "#888888" }}>({timeZone})</span>
|
||||
</>
|
||||
}
|
||||
withSpacer
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { Info } from "./Info";
|
||||
|
||||
const PersonInfo = ({ name = "", email = "", role = "" }) => (
|
||||
<div style={{ color: "#494949", fontWeight: 400, lineHeight: "24px" }}>
|
||||
{name} - {role}{" "}
|
||||
<span style={{ color: "#888888" }}>
|
||||
<a href={`mailto:${email}`} style={{ color: "#888888" }}>
|
||||
{email}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export function WhoInfo(props: { calEvent: CalendarEvent }) {
|
||||
const t = props.calEvent.attendees[0].language.translate;
|
||||
return (
|
||||
<Info
|
||||
label={t("who")}
|
||||
description={
|
||||
<>
|
||||
<PersonInfo
|
||||
name={props.calEvent.organizer.name}
|
||||
role={t("organizer")}
|
||||
email={props.calEvent.organizer.email}
|
||||
/>
|
||||
{props.calEvent.attendees.map((attendee) => (
|
||||
<PersonInfo
|
||||
key={attendee.id || attendee.name}
|
||||
name={attendee.name}
|
||||
role={t("guest")}
|
||||
email={attendee.email}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
withSpacer
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export { BaseEmailHtml } from "./BaseEmailHtml";
|
||||
export { CallToAction } from "./CallToAction";
|
||||
export { CallToActionTable } from "./CallToActionTable";
|
||||
export { CustomInputs } from "./CustomInputs";
|
||||
export { Info } from "./Info";
|
||||
export { LinkIcon } from "./LinkIcon";
|
||||
export { LocationInfo } from "./LocationInfo";
|
||||
export { ManageLink } from "./ManageLink";
|
||||
export { default as RawHtml } from "./RawHtml";
|
||||
export { WhenInfo } from "./WhenInfo";
|
||||
export { WhoInfo } from "./WhoInfo";
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as ReactDOMServer from "react-dom/server";
|
||||
|
||||
import * as templates from "./templates";
|
||||
|
||||
function renderEmail<K extends keyof typeof templates>(
|
||||
template: K,
|
||||
props: React.ComponentProps<typeof templates[K]>
|
||||
) {
|
||||
const Component = templates[template];
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
ReactDOMServer.renderToStaticMarkup(Component(props))
|
||||
// Remove `<RawHtml />` injected scripts
|
||||
.replace(/<script><\/script>/g, "")
|
||||
.replace(
|
||||
"<html>",
|
||||
`<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default renderEmail;
|
||||
@@ -0,0 +1,24 @@
|
||||
import { CallToAction, CallToActionTable } from "../components";
|
||||
import { AttendeeScheduledEmail } from "./AttendeeScheduledEmail";
|
||||
|
||||
function ManageLink(props: React.ComponentProps<typeof AttendeeScheduledEmail>) {
|
||||
const manageText = props.attendee.language.translate("pay_now");
|
||||
|
||||
if (!props.calEvent.paymentInfo?.link) return null;
|
||||
|
||||
return (
|
||||
<CallToActionTable>
|
||||
<CallToAction label={manageText} href={props.calEvent.paymentInfo.link} />
|
||||
</CallToActionTable>
|
||||
);
|
||||
}
|
||||
|
||||
export const AttendeeAwaitingPaymentEmail = (props: React.ComponentProps<typeof AttendeeScheduledEmail>) => (
|
||||
<AttendeeScheduledEmail
|
||||
title="meeting_awaiting_payment"
|
||||
headerType="calendarCircle"
|
||||
subject="awaiting_payment_subject"
|
||||
callToAction={<ManageLink {...props} />}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
import { AttendeeScheduledEmail } from "./AttendeeScheduledEmail";
|
||||
|
||||
export const AttendeeCancelledEmail = (props: React.ComponentProps<typeof AttendeeScheduledEmail>) => (
|
||||
<AttendeeScheduledEmail
|
||||
title="event_request_cancelled"
|
||||
headerType="xCircle"
|
||||
subject="event_cancelled_subject"
|
||||
callToAction={null}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
import { AttendeeScheduledEmail } from "./AttendeeScheduledEmail";
|
||||
|
||||
export const AttendeeDeclinedEmail = (props: React.ComponentProps<typeof AttendeeScheduledEmail>) => (
|
||||
<AttendeeScheduledEmail
|
||||
title={props.recurringEvent?.count ? "event_request_declined_recurring" : "event_request_declined"}
|
||||
headerType="xCircle"
|
||||
subject="event_declined_subject"
|
||||
callToAction={null}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
import { AttendeeScheduledEmail } from "./AttendeeScheduledEmail";
|
||||
|
||||
export const AttendeeLocationChangeEmail = (props: React.ComponentProps<typeof AttendeeScheduledEmail>) => (
|
||||
<AttendeeScheduledEmail
|
||||
title="event_location_changed"
|
||||
headerType="calendarCircle"
|
||||
subject="location_changed_event_type_subject"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,18 @@
|
||||
import { AttendeeScheduledEmail } from "./AttendeeScheduledEmail";
|
||||
|
||||
export const AttendeeRequestEmail = (props: React.ComponentProps<typeof AttendeeScheduledEmail>) => (
|
||||
<AttendeeScheduledEmail
|
||||
title={props.calEvent.organizer.language.translate(
|
||||
props.recurringEvent?.count ? "booking_submitted_recurring" : "booking_submitted"
|
||||
)}
|
||||
subtitle={props.calEvent.organizer.language.translate(
|
||||
props.recurringEvent.count
|
||||
? "user_needs_to_confirm_or_reject_booking_recurring"
|
||||
: "user_needs_to_confirm_or_reject_booking",
|
||||
{ user: props.calEvent.organizer.name }
|
||||
)}
|
||||
headerType="calendarCircle"
|
||||
subject="booking_submitted_subject"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
import { CallToAction, CallToActionTable } from "../components";
|
||||
import { OrganizerScheduledEmail } from "./OrganizerScheduledEmail";
|
||||
|
||||
export const AttendeeRequestRescheduledEmail = (
|
||||
props: { metadata: { rescheduleLink: string } } & React.ComponentProps<typeof OrganizerScheduledEmail>
|
||||
) => (
|
||||
<OrganizerScheduledEmail
|
||||
title="request_reschedule_title_attendee"
|
||||
subtitle={props.calEvent.organizer.language.translate("request_reschedule_subtitle", {
|
||||
organizer: props.calEvent.organizer.name,
|
||||
})}
|
||||
headerType="calendarCircle"
|
||||
subject="rescheduled_event_type_subject"
|
||||
callToAction={
|
||||
<CallToActionTable>
|
||||
<CallToAction label="Book a new time" href={props.metadata.rescheduleLink} />
|
||||
</CallToActionTable>
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
import { AttendeeScheduledEmail } from "./AttendeeScheduledEmail";
|
||||
|
||||
export const AttendeeRescheduledEmail = (props: React.ComponentProps<typeof AttendeeScheduledEmail>) => (
|
||||
<AttendeeScheduledEmail
|
||||
title="event_has_been_rescheduled"
|
||||
headerType="calendarCircle"
|
||||
subject="rescheduled_event_type_subject"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { CalendarEvent, Person, RecurringEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { BaseScheduledEmail } from "./BaseScheduledEmail";
|
||||
|
||||
export const AttendeeScheduledEmail = (
|
||||
props: {
|
||||
calEvent: CalendarEvent;
|
||||
attendee: Person;
|
||||
recurringEvent: RecurringEvent;
|
||||
} & Partial<React.ComponentProps<typeof BaseScheduledEmail>>
|
||||
) => {
|
||||
return (
|
||||
<BaseScheduledEmail timeZone={props.attendee.timeZone} t={props.attendee.language.translate} {...props} />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import toArray from "dayjs/plugin/toArray";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { TFunction } from "next-i18next";
|
||||
|
||||
import type { CalendarEvent, Person, RecurringEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import {
|
||||
BaseEmailHtml,
|
||||
CustomInputs,
|
||||
Info,
|
||||
LocationInfo,
|
||||
ManageLink,
|
||||
WhenInfo,
|
||||
WhoInfo,
|
||||
} from "../components";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(localizedFormat);
|
||||
dayjs.extend(toArray);
|
||||
|
||||
export const BaseScheduledEmail = (
|
||||
props: {
|
||||
calEvent: CalendarEvent;
|
||||
attendee: Person;
|
||||
recurringEvent: RecurringEvent;
|
||||
timeZone: string;
|
||||
t: TFunction;
|
||||
} & Partial<React.ComponentProps<typeof BaseEmailHtml>>
|
||||
) => {
|
||||
const { t, timeZone } = props;
|
||||
|
||||
function getRecipientStart(format: string) {
|
||||
return dayjs(props.calEvent.startTime).tz(timeZone).format(format);
|
||||
}
|
||||
|
||||
function getRecipientEnd(format: string) {
|
||||
return dayjs(props.calEvent.endTime).tz(timeZone).format(format);
|
||||
}
|
||||
|
||||
const subject = t(props.subject || "confirmed_event_type_subject", {
|
||||
eventType: props.calEvent.type,
|
||||
name: props.calEvent.team?.name || props.calEvent.organizer.name,
|
||||
date: `${getRecipientStart("h:mma")} - ${getRecipientEnd("h:mma")}, ${t(
|
||||
getRecipientStart("dddd").toLowerCase()
|
||||
)}, ${t(getRecipientStart("MMMM").toLowerCase())} ${getRecipientStart("D, YYYY")}`,
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseEmailHtml
|
||||
headerType={props.headerType || "checkCircle"}
|
||||
subject={props.subject || subject}
|
||||
title={t(
|
||||
props.title
|
||||
? props.title
|
||||
: props.recurringEvent?.count
|
||||
? "your_event_has_been_scheduled_recurring"
|
||||
: "your_event_has_been_scheduled"
|
||||
)}
|
||||
callToAction={
|
||||
props.callToAction === null
|
||||
? null
|
||||
: props.callToAction || <ManageLink attendee={props.attendee} calEvent={props.calEvent} />
|
||||
}
|
||||
subtitle={t(props.subtitle || "emailed_you_and_any_other_attendees")}>
|
||||
<Info label={t("cancellation_reason")} description={props.calEvent.cancellationReason} withSpacer />
|
||||
<Info label={t("rejection_reason")} description={props.calEvent.rejectionReason} withSpacer />
|
||||
<Info label={t("what")} description={props.calEvent.type} withSpacer />
|
||||
<WhenInfo calEvent={props.calEvent} recurringEvent={props.recurringEvent} t={t} timeZone={timeZone} />
|
||||
<WhoInfo calEvent={props.calEvent} />
|
||||
<LocationInfo calEvent={props.calEvent} />
|
||||
<Info label={t("description")} description={props.calEvent.description} withSpacer />
|
||||
<Info label={t("additional_notes")} description={props.calEvent.additionalNotes} withSpacer />
|
||||
<CustomInputs calEvent={props.calEvent} />
|
||||
</BaseEmailHtml>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { BaseEmailHtml, Info } from "../components";
|
||||
|
||||
export interface Feedback {
|
||||
userId: number;
|
||||
rating: string;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
export const FeedbackEmail = (props: Feedback & Partial<React.ComponentProps<typeof BaseEmailHtml>>) => {
|
||||
return (
|
||||
<BaseEmailHtml subject="Feedback" title="Feedback">
|
||||
<Info label="User id" description={props.userId} withSpacer />
|
||||
<Info label="Rating" description={props.rating} withSpacer />
|
||||
<Info label="Comment" description={props.comment} withSpacer />
|
||||
</BaseEmailHtml>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import { TFunction } from "next-i18next";
|
||||
|
||||
import { BaseEmailHtml, CallToAction } from "../components";
|
||||
|
||||
export type PasswordReset = {
|
||||
language: TFunction;
|
||||
user: {
|
||||
name?: string | null;
|
||||
email: string;
|
||||
};
|
||||
resetLink: string;
|
||||
};
|
||||
export const ForgotPasswordEmail = (
|
||||
props: PasswordReset & Partial<React.ComponentProps<typeof BaseEmailHtml>>
|
||||
) => {
|
||||
return (
|
||||
<BaseEmailHtml subject={props.language("reset_password_subject")}>
|
||||
<p>
|
||||
<>{props.language("hi_user_name", { user: props.user.name })}!</>
|
||||
</p>
|
||||
<p style={{ fontWeight: 400, lineHeight: "24px" }}>
|
||||
<>{props.language("someone_requested_password_reset")}</>
|
||||
</p>
|
||||
<CallToAction label={props.language("change_password")} href={props.resetLink} />
|
||||
|
||||
<div style={{ lineHeight: "6px" }}>
|
||||
<p style={{ fontWeight: 400, lineHeight: "24px" }}>
|
||||
<>{props.language("password_reset_instructions")}</>
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ lineHeight: "6px" }}>
|
||||
<p style={{ fontWeight: 400, lineHeight: "24px" }}>
|
||||
<>
|
||||
{props.language("have_any_questions")}{" "}
|
||||
<a href="mailto:support@cal.com" style={{ color: "#3E3E3E" }} target="_blank" rel="noreferrer">
|
||||
<>{props.language("contact_our_support_team")}</>
|
||||
</a>
|
||||
</>
|
||||
</p>
|
||||
</div>
|
||||
</BaseEmailHtml>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { OrganizerScheduledEmail } from "./OrganizerScheduledEmail";
|
||||
|
||||
export const OrganizerCancelledEmail = (props: React.ComponentProps<typeof OrganizerScheduledEmail>) => (
|
||||
<OrganizerScheduledEmail
|
||||
title="event_request_cancelled"
|
||||
headerType="xCircle"
|
||||
subject="event_cancelled_subject"
|
||||
callToAction={null}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
import { OrganizerScheduledEmail } from "./OrganizerScheduledEmail";
|
||||
|
||||
export const OrganizerLocationChangeEmail = (props: React.ComponentProps<typeof OrganizerScheduledEmail>) => (
|
||||
<OrganizerScheduledEmail
|
||||
title="event_location_changed"
|
||||
headerType="calendarCircle"
|
||||
subject="location_changed_event_type_subject"
|
||||
callToAction={null}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,67 @@
|
||||
import { BaseEmailHtml } from "../components";
|
||||
import { OrganizerScheduledEmail } from "./OrganizerScheduledEmail";
|
||||
|
||||
export const OrganizerPaymentRefundFailedEmail = (
|
||||
props: React.ComponentProps<typeof OrganizerScheduledEmail>
|
||||
) => {
|
||||
const t = props.calEvent.organizer.language.translate;
|
||||
|
||||
return (
|
||||
<BaseEmailHtml
|
||||
headerType="xCircle"
|
||||
subject="refund_failed_subject"
|
||||
title={t("a_refund_failed")}
|
||||
callToAction={null}
|
||||
subtitle={t("check_with_provider_and_user", {
|
||||
user: props.calEvent.attendees[0].name,
|
||||
})}>
|
||||
<RefundInformation {...props} />
|
||||
</BaseEmailHtml>
|
||||
);
|
||||
};
|
||||
|
||||
function RefundInformation(props: React.ComponentProps<typeof OrganizerPaymentRefundFailedEmail>) {
|
||||
const { paymentInfo } = props.calEvent;
|
||||
const t = props.calEvent.organizer.language.translate;
|
||||
|
||||
if (!paymentInfo) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{paymentInfo.reason && (
|
||||
<tr>
|
||||
<td align="center" style={{ fontSize: "0px", padding: "10px 25px", wordBreak: "break-word" }}>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: "Roboto, Helvetica, sans-serif",
|
||||
fontSize: "16px",
|
||||
fontWeight: 400,
|
||||
lineHeight: "24px",
|
||||
textAlign: "center",
|
||||
color: "#494949",
|
||||
}}>
|
||||
{t("error_message", { errorMessage: paymentInfo.reason }).toString()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{paymentInfo.id && (
|
||||
<tr>
|
||||
<td align="center" style={{ fontSize: "0px", padding: "10px 25px", wordBreak: "break-word" }}>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: "Roboto, Helvetica, sans-serif",
|
||||
fontSize: "16px",
|
||||
fontWeight: 400,
|
||||
lineHeight: "24px",
|
||||
textAlign: "center",
|
||||
color: "#494949",
|
||||
}}>
|
||||
Payment {paymentInfo.id}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { CallToAction, CallToActionTable } from "../components";
|
||||
import { OrganizerScheduledEmail } from "./OrganizerScheduledEmail";
|
||||
|
||||
export const OrganizerRequestEmail = (props: React.ComponentProps<typeof OrganizerScheduledEmail>) => (
|
||||
<OrganizerScheduledEmail
|
||||
title={
|
||||
props.title || props.recurringEvent?.count
|
||||
? "event_awaiting_approval_recurring"
|
||||
: "event_awaiting_approval"
|
||||
}
|
||||
subtitle="someone_requested_an_event"
|
||||
headerType="calendarCircle"
|
||||
subject="event_awaiting_approval_subject"
|
||||
callToAction={
|
||||
<CallToActionTable>
|
||||
<CallToAction
|
||||
label={props.calEvent.organizer.language.translate("confirm_or_reject_request")}
|
||||
href={process.env.NEXT_PUBLIC_WEBAPP_URL + "/bookings/upcoming"}
|
||||
/>
|
||||
</CallToActionTable>
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
Loaded 100 of 137 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user