feat: add option to provide cancellation reason for email (#1587)
* feat: add option to provide cancellation reason for email * chore: move pos of getCancellationReason method in classes * fix: only show cancellation reason if givenpull/1642/head^2
parent
8d0861809c
commit
07b75dadbd
|
@ -48,6 +48,7 @@ ${this.getWhat()}
|
|||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
|
@ -95,6 +96,7 @@ ${this.getAdditionalNotes()}
|
|||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -126,4 +128,13 @@ ${this.getAdditionalNotes()}
|
|||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
protected getCancellationReason(): string {
|
||||
return `
|
||||
<p style="height: 6px"></p>
|
||||
<div style="line-height: 6px;">
|
||||
<p style="color: #494949;">${this.calEvent.attendees[0].language.translate("cancellation_reason")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.cancellationReason}</p>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ ${this.getWhat()}
|
|||
${this.getWhen()}
|
||||
${this.getLocation()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||
`.replace(/(<([^>]+)>)/gi, "");
|
||||
}
|
||||
|
||||
|
@ -103,6 +104,7 @@ ${this.getAdditionalNotes()}
|
|||
${this.getWho()}
|
||||
${this.getLocation()}
|
||||
${this.getAdditionalNotes()}
|
||||
${this.calEvent.cancellationReason && this.getCancellationReason()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -134,4 +136,13 @@ ${this.getAdditionalNotes()}
|
|||
</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>`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ export interface CalendarEvent {
|
|||
videoCallData?: VideoCallData;
|
||||
paymentInfo?: PaymentInfo | null;
|
||||
destinationCalendar?: DestinationCalendar | null;
|
||||
cancellationReason?: string | null;
|
||||
}
|
||||
|
||||
export interface IntegrationCalendar extends Ensure<Partial<SelectedCalendar>, "externalId"> {
|
||||
|
|
|
@ -25,6 +25,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
}
|
||||
|
||||
const uid = asStringOrNull(req.body.uid) || "";
|
||||
const cancellationReason = asStringOrNull(req.body.reason) || "";
|
||||
const session = await getSession({ req: req });
|
||||
|
||||
const bookingToDelete = await prisma.booking.findUnique({
|
||||
|
@ -125,6 +126,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
uid: bookingToDelete?.uid,
|
||||
location: bookingToDelete?.location,
|
||||
destinationCalendar: bookingToDelete?.destinationCalendar || bookingToDelete?.user.destinationCalendar,
|
||||
cancellationReason: cancellationReason,
|
||||
};
|
||||
|
||||
// Hook up the webhook logic here
|
||||
|
@ -148,6 +150,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
},
|
||||
data: {
|
||||
status: BookingStatus.CANCELLED,
|
||||
cancellationReason: cancellationReason,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/t
|
|||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
import CustomBranding from "@components/CustomBranding";
|
||||
import { TextField } from "@components/form/fields";
|
||||
import { HeadSeo } from "@components/seo/head-seo";
|
||||
import { Button } from "@components/ui/Button";
|
||||
|
||||
|
@ -25,6 +26,7 @@ export default function Type(props: inferSSRProps<typeof getServerSideProps>) {
|
|||
const [is24h] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(props.booking ? null : t("booking_already_cancelled"));
|
||||
const [cancellationReason, setCancellationReason] = useState<string>("");
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
return (
|
||||
|
@ -89,50 +91,60 @@ export default function Type(props: inferSSRProps<typeof getServerSideProps>) {
|
|||
</div>
|
||||
</div>
|
||||
{props.cancellationAllowed && (
|
||||
<div className="mt-5 space-x-2 text-center sm:mt-6">
|
||||
<Button
|
||||
color="secondary"
|
||||
data-testid="cancel"
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
<div className="mt-5 sm:mt-6">
|
||||
<TextField
|
||||
name={t("cancellation_reason")}
|
||||
placeholder={t("cancellation_reason_placeholder")}
|
||||
value={cancellationReason}
|
||||
onChange={(e) => setCancellationReason(e.target.value)}
|
||||
className="mb-5 sm:mb-6"
|
||||
/>
|
||||
<div className="text-center space-x-2">
|
||||
<Button
|
||||
color="secondary"
|
||||
data-testid="cancel"
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
|
||||
const payload = {
|
||||
uid: uid,
|
||||
};
|
||||
const payload = {
|
||||
uid: uid,
|
||||
reason: cancellationReason,
|
||||
};
|
||||
|
||||
telemetry.withJitsu((jitsu) =>
|
||||
jitsu.track(telemetryEventTypes.bookingCancelled, collectPageParameters())
|
||||
);
|
||||
|
||||
const res = await fetch("/api/cancel", {
|
||||
body: JSON.stringify(payload),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
if (res.status >= 200 && res.status < 300) {
|
||||
await router.push(
|
||||
`/cancel/success?name=${props.profile.name}&title=${
|
||||
props.booking.title
|
||||
}&eventPage=${props.profile.slug}&team=${
|
||||
props.booking.eventType?.team ? 1 : 0
|
||||
}`
|
||||
telemetry.withJitsu((jitsu) =>
|
||||
jitsu.track(telemetryEventTypes.bookingCancelled, collectPageParameters())
|
||||
);
|
||||
} else {
|
||||
setLoading(false);
|
||||
setError(
|
||||
`${t("error_with_status_code_occured", { status: res.status })} ${t(
|
||||
"please_try_again"
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}}
|
||||
loading={loading}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<Button onClick={() => router.push("/reschedule/" + uid)}>{t("reschedule")}</Button>
|
||||
|
||||
const res = await fetch("/api/cancel", {
|
||||
body: JSON.stringify(payload),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
if (res.status >= 200 && res.status < 300) {
|
||||
await router.push(
|
||||
`/cancel/success?name=${props.profile.name}&title=${
|
||||
props.booking.title
|
||||
}&eventPage=${props.profile.slug}&team=${
|
||||
props.booking.eventType?.team ? 1 : 0
|
||||
}`
|
||||
);
|
||||
} else {
|
||||
setLoading(false);
|
||||
setError(
|
||||
`${t("error_with_status_code_occured", { status: res.status })} ${t(
|
||||
"please_try_again"
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}}
|
||||
loading={loading}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<Button onClick={() => router.push("/reschedule/" + uid)}>{t("reschedule")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "Booking" ADD COLUMN "cancellationReason" TEXT;
|
|
@ -242,6 +242,7 @@ model Booking {
|
|||
paid Boolean @default(false)
|
||||
payment Payment[]
|
||||
destinationCalendar DestinationCalendar?
|
||||
cancellationReason String?
|
||||
}
|
||||
|
||||
model Schedule {
|
||||
|
|
|
@ -35,6 +35,7 @@ export const _BookingModel = z.object({
|
|||
rejected: z.boolean(),
|
||||
status: z.nativeEnum(BookingStatus),
|
||||
paid: z.boolean(),
|
||||
cancellationReason: z.string().nullish(),
|
||||
});
|
||||
|
||||
export interface CompleteBooking extends z.infer<typeof _BookingModel> {
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
"event_request_cancelled": "Your scheduled event was cancelled",
|
||||
"organizer": "Organizer",
|
||||
"need_to_reschedule_or_cancel": "Need to reschedule or cancel?",
|
||||
"cancellation_reason": "Reason for cancellation",
|
||||
"cancellation_reason_placeholder": "Why are you cancelling? (optional)",
|
||||
"manage_this_event": "Manage this event",
|
||||
"your_event_has_been_scheduled": "Your event has been scheduled",
|
||||
"accept_our_license": "Accept our license by changing the .env variable <1>NEXT_PUBLIC_LICENSE_CONSENT</1> to '{{agree}}'.",
|
||||
|
|
Loading…
Reference in New Issue