Add reschedule reason to DB and emails (#2909)
* Write reschedule reason to db * Add reschedule reason to emails * Add reschedule reason to request reschedule email * Add reschedule reason to request reschedule email * Add reschedule reason to request reschedule email * Add reschedule reason to request reschedule email * Write reschedule reason to db * Add reschedule reason to emails * No longer using rescheduleReason in favor of cancellationReason * Update apps emails accordingly with reschedule reason * Update reschedule.test.ts Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: Alan <alannnc@gmail.com> Co-authored-by: zomars <zomars@me.com>
This commit is contained in:
17 files changed
+103
-15
No files matched your search
@@ -83,6 +83,7 @@ type BookingFormValues = {
|
||||
customInputs?: {
|
||||
[key: string]: string | boolean;
|
||||
};
|
||||
rescheduleReason?: string;
|
||||
};
|
||||
|
||||
const BookingPage = ({
|
||||
@@ -255,6 +256,7 @@ const BookingPage = ({
|
||||
email: primaryAttendee.email || "",
|
||||
guests: guestListEmails,
|
||||
notes: booking.description || "",
|
||||
rescheduleReason: "",
|
||||
customInputs: eventType.customInputs.reduce(
|
||||
(customInputs, input) => ({
|
||||
...customInputs,
|
||||
@@ -786,18 +788,31 @@ const BookingPage = ({
|
||||
<label
|
||||
htmlFor="notes"
|
||||
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("additional_notes")}
|
||||
{rescheduleUid ? t("reschedule_optional") : t("additional_notes")}
|
||||
</label>
|
||||
<textarea
|
||||
{...bookingForm.register("notes")}
|
||||
id="notes"
|
||||
name="notes"
|
||||
rows={3}
|
||||
className={inputClassName}
|
||||
placeholder={t("share_additional_notes")}
|
||||
disabled={disabledExceptForOwner}
|
||||
/>
|
||||
{rescheduleUid ? (
|
||||
<textarea
|
||||
{...bookingForm.register("rescheduleReason")}
|
||||
id="rescheduleReason"
|
||||
name="rescheduleReason"
|
||||
rows={3}
|
||||
className={inputClassName}
|
||||
placeholder={t("reschedule_placeholder")}
|
||||
disabled={disabledExceptForOwner}
|
||||
/>
|
||||
) : (
|
||||
<textarea
|
||||
{...bookingForm.register("notes")}
|
||||
id="notes"
|
||||
name="notes"
|
||||
rows={3}
|
||||
className={inputClassName}
|
||||
placeholder={t("share_additional_notes")}
|
||||
disabled={disabledExceptForOwner}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-2 rtl:space-x-reverse">
|
||||
<Button
|
||||
type="submit"
|
||||
|
||||
@@ -102,6 +102,7 @@ ${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()}
|
||||
@@ -151,6 +152,7 @@ ${getCancelLink(this.calEvent)}
|
||||
<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()}
|
||||
|
||||
@@ -52,6 +52,7 @@ export default class AttendeeRescheduledEmail extends AttendeeScheduledEmail {
|
||||
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()}
|
||||
@@ -112,6 +113,7 @@ ${this.getCustomInputs()}
|
||||
<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()}
|
||||
|
||||
@@ -452,4 +452,12 @@ ${getRichDescription(this.calEvent)}
|
||||
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>`;
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,7 @@ ${this.calEvent.organizer.language.translate("request_reschedule_title_organizer
|
||||
${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()}
|
||||
@@ -163,6 +164,7 @@ ${getCancelLink(this.calEvent)}
|
||||
<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()}
|
||||
|
||||
@@ -59,6 +59,7 @@ export default class OrganizerRescheduledEmail extends OrganizerScheduledEmail {
|
||||
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()}
|
||||
@@ -108,6 +109,7 @@ ${getCancelLink(this.calEvent)}
|
||||
<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()}
|
||||
|
||||
@@ -439,4 +439,12 @@ ${getRichDescription(this.calEvent)}
|
||||
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>`;
|
||||
}
|
||||
}
|
||||
@@ -201,7 +201,11 @@ const getEventTypesFromDB = async (eventTypeId: number) => {
|
||||
|
||||
type User = Prisma.UserGetPayload<typeof userSelect>;
|
||||
|
||||
type ExtendedBookingCreateBody = BookingCreateBody & { noEmail?: boolean; recurringCount?: number };
|
||||
type ExtendedBookingCreateBody = BookingCreateBody & {
|
||||
noEmail?: boolean;
|
||||
recurringCount?: number;
|
||||
rescheduleReason?: string;
|
||||
};
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { recurringCount, noEmail, ...reqBody } = req.body as ExtendedBookingCreateBody;
|
||||
@@ -677,11 +681,18 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
if (originalRescheduledBooking?.uid) {
|
||||
// Use EventManager to conditionally use all needed integrations.
|
||||
const updateManager = await eventManager.update(evt, originalRescheduledBooking.uid, booking?.id);
|
||||
const updateManager = await eventManager.update(
|
||||
evt,
|
||||
originalRescheduledBooking.uid,
|
||||
booking?.id,
|
||||
reqBody.rescheduleReason
|
||||
);
|
||||
// This gets overridden when updating the event - to check if notes have been hidden or not. We just reset this back
|
||||
// to the default description when we are sending the emails.
|
||||
evt.description = eventType.description;
|
||||
|
||||
console.log("🚀 ~ file: event.ts ~ line 684 ~ handler ~ descripiton", reqBody);
|
||||
|
||||
results = updateManager.results;
|
||||
referencesToCreate = updateManager.referencesToCreate;
|
||||
if (results.length > 0 && results.some((res) => !res.success)) {
|
||||
@@ -711,7 +722,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
{
|
||||
...evt,
|
||||
additionInformation: metadata,
|
||||
additionalNotes, // Resets back to the addtionalNote input and not the overriden value
|
||||
additionalNotes, // Resets back to the additionalNote input and not the override value
|
||||
cancellationReason: reqBody.rescheduleReason,
|
||||
},
|
||||
reqBody.recurringEventId ? (eventType.recurringEvent as RecurringEvent) : {}
|
||||
);
|
||||
|
||||
@@ -87,7 +87,7 @@ test.describe("Reschedule Tests", async () => {
|
||||
|
||||
await expect(page.locator('[name="name"]')).toBeDisabled();
|
||||
await expect(page.locator('[name="email"]')).toBeDisabled();
|
||||
await expect(page.locator('[name="notes"]')).toBeDisabled();
|
||||
await expect(page.locator('[name="rescheduleReason"]')).toBeDisabled();
|
||||
|
||||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
|
||||
|
||||
@@ -868,6 +868,8 @@
|
||||
"go_to": "Go to: ",
|
||||
"zapier_invite_link": "Zapier Invite Link",
|
||||
"meeting_url_provided_after_confirmed":"A Meeting URL will be created once the event is confirmed.",
|
||||
"reschedule_optional": "Reason for rescheduling (optional)",
|
||||
"reschedule_placeholder": "Let others know why you need to reschedule",
|
||||
"event_cancelled":"This event is cancelled",
|
||||
"emailed_information_about_cancelled_event": "We emailed you and the other attendees to let them know.",
|
||||
"this_input_will_shown_booking_this_event": "This input will be shown when booking this event",
|
||||
|
||||
@@ -102,6 +102,7 @@ ${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()}
|
||||
@@ -150,6 +151,7 @@ ${getCancelLink(this.calEvent)}
|
||||
<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()}
|
||||
|
||||
@@ -406,4 +406,12 @@ ${getRichDescription(this.calEvent)}
|
||||
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>`;
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,7 @@ ${this.calEvent.organizer.language.translate("request_reschedule_title_organizer
|
||||
${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()}
|
||||
@@ -162,6 +163,7 @@ ${getCancelLink(this.calEvent)}
|
||||
<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()}
|
||||
|
||||
@@ -102,6 +102,7 @@ ${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()}
|
||||
@@ -150,6 +151,7 @@ ${getCancelLink(this.calEvent)}
|
||||
<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()}
|
||||
|
||||
@@ -406,4 +406,12 @@ ${getRichDescription(this.calEvent)}
|
||||
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>`;
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,7 @@ ${this.calEvent.organizer.language.translate("request_reschedule_title_organizer
|
||||
${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()}
|
||||
@@ -162,6 +163,7 @@ ${getCancelLink(this.calEvent)}
|
||||
<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()}
|
||||
|
||||
@@ -165,7 +165,8 @@ export default class EventManager {
|
||||
public async update(
|
||||
event: CalendarEvent,
|
||||
rescheduleUid: string,
|
||||
newBookingId?: number
|
||||
newBookingId?: number,
|
||||
rescheduleReason?: string
|
||||
): Promise<CreateUpdateResult> {
|
||||
const evt = processLocation(event);
|
||||
|
||||
@@ -201,6 +202,16 @@ export default class EventManager {
|
||||
throw new Error("booking not found");
|
||||
}
|
||||
|
||||
// Add reschedule reason to new booking
|
||||
await prisma.booking.update({
|
||||
where: {
|
||||
id: newBookingId,
|
||||
},
|
||||
data: {
|
||||
cancellationReason: rescheduleReason,
|
||||
},
|
||||
});
|
||||
|
||||
const isDedicated = evt.location ? isDedicatedIntegration(evt.location) : null;
|
||||
const results: Array<EventResult> = [];
|
||||
// If and only if event type is a dedicated meeting, update the dedicated video meeting.
|
||||
|
||||
Reference in new issue
Block a user