feat: dynamic time format rendering in workflows based on user settings (#10229)

This commit is contained in:
Monto 2023-07-19 17:30:37 +03:00 committed by GitHub
parent 1ad4f7247e
commit 26e458be6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 205 additions and 56 deletions

View File

@ -22,6 +22,7 @@ import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
import { handleRefundError } from "@calcom/lib/payment/handleRefundError";
import { getTranslation } from "@calcom/lib/server/i18n";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import { BookingStatus, MembershipRole, WorkflowMethods, WebhookTriggerEvents } from "@calcom/prisma/enums";
import { schemaBookingCancelParams } from "@calcom/prisma/zod-utils";
@ -44,6 +45,7 @@ async function getBookingToDelete(id: number | undefined, uid: string | undefine
credentials: true, // Not leaking at the moment, be careful with
email: true,
timeZone: true,
timeFormat: true,
name: true,
destinationCalendar: true,
},
@ -154,6 +156,7 @@ async function handler(req: CustomRequest) {
name: true,
email: true,
timeZone: true,
timeFormat: true,
locale: true,
},
});
@ -207,6 +210,7 @@ async function handler(req: CustomRequest) {
email: organizer.email,
name: organizer.name ?? "Nameless",
timeZone: organizer.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(organizer.timeFormat),
language: { translate: tOrganizer, locale: organizer.locale ?? "en" },
},
attendees: attendeesList,
@ -540,6 +544,7 @@ async function handler(req: CustomRequest) {
email: bookingToDelete.user?.email ?? "dev@calendso.com",
name: bookingToDelete.user?.name ?? "no user",
timeZone: bookingToDelete.user?.timeZone ?? "",
timeFormat: getTimeFormatStringFromUserTimeFormat(organizer.timeFormat),
language: { translate: tOrganizer, locale: organizer.locale ?? "en" },
},
attendees: attendeesList,

View File

@ -60,7 +60,7 @@ import { getBookerUrl } from "@calcom/lib/server/getBookerUrl";
import { getTranslation } from "@calcom/lib/server/i18n";
import { slugify } from "@calcom/lib/slugify";
import { updateWebUser as syncServicesUpdateWebUser } from "@calcom/lib/sync/SyncServiceManager";
import { TimeFormat } from "@calcom/lib/timeFormat";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import prisma, { userSelect } from "@calcom/prisma";
import type { BookingReference } from "@calcom/prisma/client";
import { BookingStatus, SchedulingType, WebhookTriggerEvents, WorkflowMethods } from "@calcom/prisma/enums";
@ -1027,7 +1027,7 @@ async function handler(
username: organizerUser.username || undefined,
timeZone: organizerUser.timeZone,
language: { translate: tOrganizer, locale: organizerUser.locale ?? "en" },
timeFormat: organizerUser.timeFormat === 24 ? TimeFormat.TWENTY_FOUR_HOUR : TimeFormat.TWELVE_HOUR,
timeFormat: getTimeFormatStringFromUserTimeFormat(organizerUser.timeFormat),
},
responses: "calEventResponses" in reqBody ? reqBody.calEventResponses : null,
userFieldsResponses: calEventUserFieldsResponses,

View File

@ -14,6 +14,7 @@ import { IS_PRODUCTION } from "@calcom/lib/constants";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import { HttpError as HttpCode } from "@calcom/lib/http-error";
import { getTranslation } from "@calcom/lib/server/i18n";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma, bookingMinimalSelect } from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
@ -59,6 +60,7 @@ async function getBooking(bookingId: number) {
id: true,
username: true,
timeZone: true,
timeFormat: true,
email: true,
name: true,
locale: true,
@ -108,6 +110,7 @@ async function getBooking(bookingId: number) {
email: user.email,
name: user.name!,
timeZone: user.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(user.timeFormat),
language: { translate: t, locale: user.locale ?? "en" },
id: user.id,
},
@ -163,6 +166,7 @@ async function handlePaymentSuccess(event: Stripe.Event) {
username: true,
credentials: true,
timeZone: true,
timeFormat: true,
email: true,
name: true,
locale: true,
@ -216,6 +220,7 @@ async function handlePaymentSuccess(event: Stripe.Event) {
email: user.email,
name: user.name!,
timeZone: user.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(user.timeFormat),
language: { translate: t, locale: user.locale ?? "en" },
},
attendees: attendeesList,

View File

@ -6,6 +6,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
import { defaultHandler } from "@calcom/lib/server";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import prisma from "@calcom/prisma";
import { WorkflowActions, WorkflowMethods, WorkflowTemplates } from "@calcom/prisma/enums";
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
@ -196,6 +197,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
reminder.workflowStep.emailSubject || "",
variables,
emailLocale,
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat),
!!reminder.booking.user?.hideBranding
).text;
emailContent.emailSubject = emailSubject;
@ -203,15 +205,22 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
reminder.workflowStep.reminderBody || "",
variables,
emailLocale,
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat),
!!reminder.booking.user?.hideBranding
).html;
emailBodyEmpty =
customTemplate(reminder.workflowStep.reminderBody || "", variables, emailLocale).text.length === 0;
customTemplate(
reminder.workflowStep.reminderBody || "",
variables,
emailLocale,
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat)
).text.length === 0;
} else if (reminder.workflowStep.template === WorkflowTemplates.REMINDER) {
emailContent = emailReminderTemplate(
false,
reminder.workflowStep.action,
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat),
reminder.booking.startTime.toISOString() || "",
reminder.booking.endTime.toISOString() || "",
reminder.booking.eventType?.title || "",

View File

@ -4,6 +4,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
import { defaultHandler } from "@calcom/lib/server";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import prisma from "@calcom/prisma";
import { WorkflowActions, WorkflowMethods, WorkflowTemplates } from "@calcom/prisma/enums";
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
@ -116,13 +117,15 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
const customMessage = customTemplate(
reminder.workflowStep.reminderBody || "",
variables,
locale || "en"
locale || "en",
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat)
);
message = customMessage.text;
} else if (reminder.workflowStep.template === WorkflowTemplates.REMINDER) {
message = smsReminderTemplate(
false,
reminder.workflowStep.action,
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat),
reminder.booking?.startTime.toISOString() || "",
reminder.booking?.eventType?.title || "",
timeZone || "",

View File

@ -4,6 +4,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
import { defaultHandler } from "@calcom/lib/server";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import prisma from "@calcom/prisma";
import { getWhatsappTemplateFunction } from "../lib/actionHelperFunctions";
@ -81,6 +82,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
const message = templateFunction(
false,
reminder.workflowStep.action,
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat),
reminder.booking?.startTime.toISOString() || "",
reminder.booking?.eventType?.title || "",
timeZone || "",

View File

@ -9,6 +9,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { WorkflowTemplates } from "@calcom/prisma/enums";
import type { WorkflowActions } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import type { RouterOutputs } from "@calcom/trpc/react";
import type { MultiSelectCheckboxesOptionType as Option } from "@calcom/ui";
import { Button, Label, MultiSelectCheckboxes, TextField } from "@calcom/ui";
import { ArrowDown, Trash2 } from "@calcom/ui/components/icon";
@ -19,12 +20,15 @@ import { AddActionDialog } from "./AddActionDialog";
import { DeleteDialog } from "./DeleteDialog";
import WorkflowStepContainer from "./WorkflowStepContainer";
type User = RouterOutputs["viewer"]["me"];
interface Props {
form: UseFormReturn<FormValues>;
workflowId: number;
selectedEventTypes: Option[];
setSelectedEventTypes: Dispatch<SetStateAction<Option[]>>;
teamId?: number;
user: User;
isMixedEventType: boolean;
readOnly: boolean;
}
@ -158,7 +162,7 @@ export default function WorkflowDetailsPage(props: Props) {
<div className="bg-muted border-subtle w-full rounded-md border p-3 py-5 md:ml-3 md:p-8">
{form.getValues("trigger") && (
<div>
<WorkflowStepContainer form={form} teamId={teamId} readOnly={props.readOnly} />
<WorkflowStepContainer form={form} user={props.user} teamId={teamId} readOnly={props.readOnly} />
</div>
)}
{form.getValues("steps") && (
@ -168,6 +172,7 @@ export default function WorkflowDetailsPage(props: Props) {
<WorkflowStepContainer
key={step.id}
form={form}
user={props.user}
step={step}
reload={reload}
setReload={setReload}

View File

@ -9,9 +9,11 @@ import { classNames } from "@calcom/lib";
import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { WorkflowTemplates, TimeUnit, WorkflowActions } from "@calcom/prisma/enums";
import { WorkflowTriggerEvents } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import type { RouterOutputs } from "@calcom/trpc/react";
import {
Badge,
Button,
@ -54,9 +56,12 @@ import { whatsappReminderTemplate } from "../lib/reminders/templates/whatsapp";
import type { FormValues } from "../pages/workflow";
import { TimeTimeUnitInput } from "./TimeTimeUnitInput";
type User = RouterOutputs["viewer"]["me"];
type WorkflowStepProps = {
step?: WorkflowStep;
form: UseFormReturn<FormValues>;
user: User;
reload?: boolean;
setReload?: Dispatch<SetStateAction<boolean>>;
teamId?: number;
@ -72,6 +77,9 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
{ teamId },
{ enabled: !!teamId }
);
const timeFormat = getTimeFormatStringFromUserTimeFormat(props.user.timeFormat);
const verifiedNumbers = _verifiedNumbers?.map((number) => number.phoneNumber) || [];
const [isAdditionalInputsDialogOpen, setIsAdditionalInputsDialogOpen] = useState(false);
@ -118,23 +126,30 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
if (!form.getValues(`steps.${step.stepNumber - 1}.reminderBody`)) {
const action = form.getValues(`steps.${step.stepNumber - 1}.action`);
if (isSMSAction(action)) {
form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, smsReminderTemplate(true, action));
form.setValue(
`steps.${step.stepNumber - 1}.reminderBody`,
smsReminderTemplate(true, action, timeFormat)
);
} else if (isWhatsappAction(action)) {
form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, whatsappReminderTemplate(true, action));
form.setValue(
`steps.${step.stepNumber - 1}.reminderBody`,
whatsappReminderTemplate(true, action, timeFormat)
);
} else {
const reminderBodyTemplate = emailReminderTemplate(true, action).emailBody;
const reminderBodyTemplate = emailReminderTemplate(true, action, timeFormat).emailBody;
form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, reminderBodyTemplate);
}
}
if (!form.getValues(`steps.${step.stepNumber - 1}.emailSubject`)) {
const subjectTemplate = emailReminderTemplate(
true,
form.getValues(`steps.${step.stepNumber - 1}.action`)
form.getValues(`steps.${step.stepNumber - 1}.action`),
timeFormat
).emailSubject;
form.setValue(`steps.${step.stepNumber - 1}.emailSubject`, subjectTemplate);
}
} else if (step && isWhatsappAction(step.action)) {
const templateBody = getWhatsappTemplateForAction(step.action, step.template);
const templateBody = getWhatsappTemplateForAction(step.action, step.template, timeFormat);
form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, templateBody);
}
@ -463,15 +478,19 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
if (isSMSAction(val.value)) {
form.setValue(
`steps.${step.stepNumber - 1}.reminderBody`,
smsReminderTemplate(true, val.value)
smsReminderTemplate(true, val.value, timeFormat)
);
} else if (isWhatsappAction(val.value)) {
form.setValue(
`steps.${step.stepNumber - 1}.reminderBody`,
whatsappReminderTemplate(true, val.value)
whatsappReminderTemplate(true, val.value, timeFormat)
);
} else {
const emailReminderBody = emailReminderTemplate(true, val.value);
const emailReminderBody = emailReminderTemplate(
true,
val.value,
timeFormat
);
form.setValue(
`steps.${step.stepNumber - 1}.reminderBody`,
emailReminderBody.emailBody
@ -680,28 +699,28 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
if (isWhatsappAction(action)) {
form.setValue(
`steps.${step.stepNumber - 1}.reminderBody`,
whatsappReminderTemplate(true, action)
whatsappReminderTemplate(true, action, timeFormat)
);
} else if (isSMSAction(action)) {
form.setValue(
`steps.${step.stepNumber - 1}.reminderBody`,
smsReminderTemplate(true, action)
smsReminderTemplate(true, action, timeFormat)
);
} else {
form.setValue(
`steps.${step.stepNumber - 1}.reminderBody`,
emailReminderTemplate(true, action).emailBody
emailReminderTemplate(true, action, timeFormat).emailBody
);
form.setValue(
`steps.${step.stepNumber - 1}.emailSubject`,
emailReminderTemplate(true, action).emailSubject
emailReminderTemplate(true, action, timeFormat).emailSubject
);
}
} else {
if (isWhatsappAction(action)) {
form.setValue(
`steps.${step.stepNumber - 1}.reminderBody`,
getWhatsappTemplateForAction(action, val.value)
getWhatsappTemplateForAction(action, val.value, timeFormat)
);
} else {
form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, "");

View File

@ -1,4 +1,7 @@
import { WorkflowActions, WorkflowTemplates, WorkflowTriggerEvents } from "@prisma/client";
import type { WorkflowTriggerEvents } from "@prisma/client";
import { WorkflowActions, WorkflowTemplates } from "@prisma/client";
import type { TimeFormat } from "@calcom/lib/timeFormat";
import {
whatsappEventCancelledTemplate,
@ -61,8 +64,9 @@ export function getWhatsappTemplateFunction(template: WorkflowTemplates): typeof
export function getWhatsappTemplateForAction(
action: WorkflowActions,
template: WorkflowTemplates
template: WorkflowTemplates,
timeFormat: TimeFormat
): string | null {
const templateFunction = getWhatsappTemplateFunction(template);
return templateFunction(true, action);
return templateFunction(true, action, timeFormat);
}

View File

@ -104,13 +104,20 @@ export const scheduleEmailReminder = async (
? evt.attendees[0].language?.locale
: evt.organizer.language.locale;
const emailSubjectTemplate = customTemplate(emailSubject, variables, locale);
const emailSubjectTemplate = customTemplate(emailSubject, variables, locale, evt.organizer.timeFormat);
emailContent.emailSubject = emailSubjectTemplate.text;
emailContent.emailBody = customTemplate(emailBody, variables, locale, hideBranding).html;
emailContent.emailBody = customTemplate(
emailBody,
variables,
locale,
evt.organizer.timeFormat,
hideBranding
).html;
} else if (template === WorkflowTemplates.REMINDER) {
emailContent = emailReminderTemplate(
false,
action,
evt.organizer.timeFormat,
startTime,
endTime,
evt.title,

View File

@ -1,5 +1,6 @@
import dayjs from "@calcom/dayjs";
import logger from "@calcom/lib/logger";
import type { TimeFormat } from "@calcom/lib/timeFormat";
import prisma from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
import type { TimeUnit } from "@calcom/prisma/enums";
@ -29,6 +30,7 @@ export type BookingInfo = {
name: string;
email: string;
timeZone: string;
timeFormat?: TimeFormat;
username?: string;
};
eventType: {
@ -115,11 +117,20 @@ export const scheduleSMSReminder = async (
cancelLink: `/booking/${evt.uid}?cancel=true`,
rescheduleLink: `/${evt.organizer.username}/${evt.eventType.slug}?rescheduleUid=${evt.uid}`,
};
const customMessage = customTemplate(message, variables, locale);
const customMessage = customTemplate(message, variables, locale, evt.organizer.timeFormat);
message = customMessage.text;
} else if (template === WorkflowTemplates.REMINDER) {
message =
smsReminderTemplate(false, action, evt.startTime, evt.title, timeZone, attendeeName, name) || message;
smsReminderTemplate(
false,
action,
evt.organizer.timeFormat,
evt.startTime,
evt.title,
timeZone,
attendeeName,
name
) || message;
}
// Allows debugging generated email content without waiting for sendgrid to send emails

View File

@ -2,6 +2,7 @@ import { guessEventLocationType } from "@calcom/app-store/locations";
import type { Dayjs } from "@calcom/dayjs";
import dayjs from "@calcom/dayjs";
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
import { TimeFormat } from "@calcom/lib/timeFormat";
import type { CalEventResponses } from "@calcom/types/Calendar";
export type VariablesType = {
@ -24,6 +25,7 @@ const customTemplate = (
text: string,
variables: VariablesType,
locale: string,
timeFormat?: TimeFormat,
isBrandingDisabled?: boolean
) => {
const translatedDate = new Intl.DateTimeFormat(locale, {
@ -42,6 +44,8 @@ const customTemplate = (
const cancelLink = variables.cancelLink ? `${WEBAPP_URL}${variables.cancelLink}` : "";
const rescheduleLink = variables.rescheduleLink ? `${WEBAPP_URL}${variables.rescheduleLink}` : "";
const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
let dynamicText = text
.replaceAll("{EVENT_NAME}", variables.eventName || "")
.replaceAll("{ORGANIZER}", variables.organizerName || "")
@ -49,9 +53,9 @@ const customTemplate = (
.replaceAll("{ORGANIZER_NAME}", variables.organizerName || "") //old variable names
.replaceAll("{ATTENDEE_NAME}", variables.attendeeName || "") //old variable names
.replaceAll("{EVENT_DATE}", translatedDate)
.replaceAll("{EVENT_TIME}", variables.eventDate?.format("H:mmA") || "")
.replaceAll("{START_TIME}", variables.eventDate?.format("H:mmA") || "")
.replaceAll("{EVENT_END_TIME}", variables.eventEndTime?.format("H:mmA") || "")
.replaceAll("{EVENT_TIME}", variables.eventDate?.format(currentTimeFormat) || "")
.replaceAll("{START_TIME}", variables.eventDate?.format(currentTimeFormat) || "")
.replaceAll("{EVENT_END_TIME}", variables.eventEndTime?.format(currentTimeFormat) || "")
.replaceAll("{LOCATION}", locationString)
.replaceAll("{ADDITIONAL_NOTES}", variables.additionalNotes || "")
.replaceAll("{ATTENDEE_EMAIL}", variables.attendeeEmail || "")

View File

@ -1,10 +1,12 @@
import dayjs from "@calcom/dayjs";
import { APP_NAME } from "@calcom/lib/constants";
import { TimeFormat } from "@calcom/lib/timeFormat";
import { WorkflowActions } from "@calcom/prisma/enums";
const emailReminderTemplate = (
isEditingMode: boolean,
action?: WorkflowActions,
timeFormat?: TimeFormat,
startTime?: string,
endTime?: string,
eventName?: string,
@ -13,6 +15,9 @@ const emailReminderTemplate = (
name?: string,
isBrandingDisabled?: boolean
) => {
const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
const dateTimeFormat = `ddd, MMM D, YYYY ${currentTimeFormat}`;
let eventDate = "";
if (isEditingMode) {
@ -21,23 +26,24 @@ const emailReminderTemplate = (
timeZone = "{TIMEZONE}";
otherPerson = action === WorkflowActions.EMAIL_ATTENDEE ? "{ORGANIZER}" : "{ATTENDEE}";
name = action === WorkflowActions.EMAIL_ATTENDEE ? "{ATTENDEE}" : "{ORGANIZER}";
eventDate = "{EVENT_DATE_ddd, MMM D, YYYY H:mmA}";
eventDate = `{EVENT_DATE_${dateTimeFormat}}`;
} else {
eventDate = dayjs(startTime).tz(timeZone).format("ddd, MMM D, YYYY H:mmA");
eventDate = dayjs(startTime).tz(timeZone).format(dateTimeFormat);
endTime = dayjs(endTime).tz(timeZone).format("H:mmA");
endTime = dayjs(endTime).tz(timeZone).format(currentTimeFormat);
}
const emailSubject = `Reminder: ${eventName} - ${eventDate}`;
const introHtml = `<body>Hi${
name ? " " + name : ""
},<br><br>This is a reminder about your upcoming event.<br><br>`;
const eventHtml = `<div><strong class="editor-text-bold">Event:</strong></div>${eventName}<br><br>`;
const eventHtml = `<div><strong class="editor-text-bold">Event: </strong></div>${eventName}<br><br>`;
const dateTimeHtml = `<div><strong class="editor-text-bold">Date & Time:</strong></div>${eventDate} - ${endTime} (${timeZone})<br><br>`;
const dateTimeHtml = `<div><strong class="editor-text-bold">Date & Time: </strong></div>${eventDate} - ${endTime} (${timeZone})<br><br>`;
const attendeeHtml = `<div><strong class="editor-text-bold">Attendees:</strong></div>You & ${otherPerson}<br><br>`;
const attendeeHtml = `<div><strong class="editor-text-bold">Attendees: </strong></div>You & ${otherPerson}<br><br>`;
const branding = !isBrandingDisabled && !isEditingMode ? `<br><br>_<br><br>Scheduling by ${APP_NAME}` : "";

View File

@ -1,27 +1,31 @@
import dayjs from "@calcom/dayjs";
import { TimeFormat } from "@calcom/lib/timeFormat";
import { WorkflowActions } from "@calcom/prisma/enums";
const smsReminderTemplate = (
isEditingMode: boolean,
action?: WorkflowActions,
timeFormat?: TimeFormat,
startTime?: string,
eventName?: string,
timeZone?: string,
attendee?: string,
name?: string
) => {
const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
let eventDate;
if (isEditingMode) {
eventName = "{EVENT_NAME}";
timeZone = "{TIMEZONE}";
startTime = "{EVENT_TIME_h:mmA}";
startTime = `{EVENT_TIME_${currentTimeFormat}}`;
eventDate = "{EVENT_DATE_YYYY MMM D}";
attendee = action === WorkflowActions.SMS_ATTENDEE ? "{ORGANIZER}" : "{ATTENDEE}";
name = action === WorkflowActions.SMS_ATTENDEE ? "{ATTENDEE}" : "{ORGANIZER}";
} else {
eventDate = dayjs(startTime).tz(timeZone).format("YYYY MMM D");
startTime = dayjs(startTime).tz(timeZone).format("h:mmA");
startTime = dayjs(startTime).tz(timeZone).format(currentTimeFormat);
}
const templateOne = `Hi${

View File

@ -1,28 +1,33 @@
import { WorkflowActions } from "@prisma/client";
import dayjs from "@calcom/dayjs";
import { TimeFormat } from "@calcom/lib/timeFormat";
export const whatsappEventCancelledTemplate = (
isEditingMode: boolean,
action?: WorkflowActions,
timeFormat?: TimeFormat,
startTime?: string,
eventName?: string,
timeZone?: string,
attendee?: string,
name?: string
) => {
const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
const dateTimeFormat = `ddd, MMM D, YYYY ${currentTimeFormat}`;
let eventDate;
if (isEditingMode) {
eventName = "{EVENT_NAME}";
timeZone = "{TIMEZONE}";
startTime = "{START_TIME_h:mmA}";
startTime = `{START_TIME_${currentTimeFormat}}`;
eventDate = "{EVENT_DATE_YYYY MMM D}";
eventDate = `{EVENT_DATE_${dateTimeFormat}}`;
attendee = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ORGANIZER}" : "{ATTENDEE}";
name = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ATTENDEE}" : "{ORGANIZER}";
} else {
eventDate = dayjs(startTime).tz(timeZone).format("YYYY MMM D");
startTime = dayjs(startTime).tz(timeZone).format("h:mmA");
startTime = dayjs(startTime).tz(timeZone).format(currentTimeFormat);
}
const templateOne = `Hi${

View File

@ -1,28 +1,33 @@
import { WorkflowActions } from "@prisma/client";
import dayjs from "@calcom/dayjs";
import { TimeFormat } from "@calcom/lib/timeFormat";
export const whatsappEventCompletedTemplate = (
isEditingMode: boolean,
action?: WorkflowActions,
timeFormat?: TimeFormat,
startTime?: string,
eventName?: string,
timeZone?: string,
attendee?: string,
name?: string
) => {
const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
const dateTimeFormat = `ddd, MMM D, YYYY ${currentTimeFormat}`;
let eventDate;
if (isEditingMode) {
eventName = "{EVENT_NAME}";
timeZone = "{TIMEZONE}";
startTime = "{START_TIME_h:mmA}";
startTime = `{START_TIME_${currentTimeFormat}}`;
eventDate = "{EVENT_DATE_YYYY MMM D}";
eventDate = `{EVENT_DATE_${dateTimeFormat}}`;
attendee = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ORGANIZER}" : "{ATTENDEE}";
name = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ATTENDEE}" : "{ORGANIZER}";
} else {
eventDate = dayjs(startTime).tz(timeZone).format("YYYY MMM D");
startTime = dayjs(startTime).tz(timeZone).format("h:mmA");
startTime = dayjs(startTime).tz(timeZone).format(currentTimeFormat);
}
const templateOne = `Hi${

View File

@ -1,28 +1,33 @@
import { WorkflowActions } from "@prisma/client";
import dayjs from "@calcom/dayjs";
import { TimeFormat } from "@calcom/lib/timeFormat";
export const whatsappReminderTemplate = (
isEditingMode: boolean,
action?: WorkflowActions,
timeFormat?: TimeFormat,
startTime?: string,
eventName?: string,
timeZone?: string,
attendee?: string,
name?: string
) => {
const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
const dateTimeFormat = `ddd, MMM D, YYYY ${currentTimeFormat}`;
let eventDate;
if (isEditingMode) {
eventName = "{EVENT_NAME}";
timeZone = "{TIMEZONE}";
startTime = "{START_TIME_h:mmA}";
startTime = `{START_TIME_${currentTimeFormat}}`;
eventDate = "{EVENT_DATE_YYYY MMM D}";
eventDate = `{EVENT_DATE_${dateTimeFormat}}`;
attendee = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ORGANIZER}" : "{ATTENDEE}";
name = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ATTENDEE}" : "{ORGANIZER}";
} else {
eventDate = dayjs(startTime).tz(timeZone).format("YYYY MMM D");
startTime = dayjs(startTime).tz(timeZone).format("h:mmA");
startTime = dayjs(startTime).tz(timeZone).format(currentTimeFormat);
}
const templateOne = `Hi${

View File

@ -1,28 +1,33 @@
import { WorkflowActions } from "@prisma/client";
import dayjs from "@calcom/dayjs";
import { TimeFormat } from "@calcom/lib/timeFormat";
export const whatsappEventRescheduledTemplate = (
isEditingMode: boolean,
action?: WorkflowActions,
timeFormat?: TimeFormat,
startTime?: string,
eventName?: string,
timeZone?: string,
attendee?: string,
name?: string
) => {
const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
const dateTimeFormat = `ddd, MMM D, YYYY ${currentTimeFormat}`;
let eventDate;
if (isEditingMode) {
eventName = "{EVENT_NAME}";
timeZone = "{TIMEZONE}";
startTime = "{START_TIME_h:mmA}";
startTime = `{START_TIME_${currentTimeFormat}}`;
eventDate = "{EVENT_DATE_YYYY MMM D}";
eventDate = `{EVENT_DATE_${dateTimeFormat}}`;
attendee = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ORGANIZER}" : "{ATTENDEE}";
name = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ATTENDEE}" : "{ORGANIZER}";
} else {
eventDate = dayjs(startTime).tz(timeZone).format("YYYY MMM D");
startTime = dayjs(startTime).tz(timeZone).format("h:mmA");
startTime = dayjs(startTime).tz(timeZone).format(currentTimeFormat);
}
const templateOne = `Hi${

View File

@ -6,7 +6,8 @@ import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma";
import * as twilio from "./smsProviders/twilioProvider";
import { BookingInfo, deleteScheduledSMSReminder, timeUnitLowerCase } from "./smsReminderManager";
import type { BookingInfo, timeUnitLowerCase } from "./smsReminderManager";
import { deleteScheduledSMSReminder } from "./smsReminderManager";
import {
whatsappEventCancelledTemplate,
whatsappEventCompletedTemplate,
@ -68,14 +69,23 @@ export const scheduleWhatsappReminder = async (
switch (template) {
case WorkflowTemplates.REMINDER:
message =
whatsappReminderTemplate(false, action, evt.startTime, evt.title, timeZone, attendeeName, name) ||
message;
whatsappReminderTemplate(
false,
action,
evt.organizer.timeFormat,
evt.startTime,
evt.title,
timeZone,
attendeeName,
name
) || message;
break;
case WorkflowTemplates.CANCELLED:
message =
whatsappEventCancelledTemplate(
false,
action,
evt.organizer.timeFormat,
evt.startTime,
evt.title,
timeZone,
@ -88,6 +98,7 @@ export const scheduleWhatsappReminder = async (
whatsappEventRescheduledTemplate(
false,
action,
evt.organizer.timeFormat,
evt.startTime,
evt.title,
timeZone,
@ -100,6 +111,7 @@ export const scheduleWhatsappReminder = async (
whatsappEventCompletedTemplate(
false,
action,
evt.organizer.timeFormat,
evt.startTime,
evt.title,
timeZone,
@ -109,8 +121,16 @@ export const scheduleWhatsappReminder = async (
break;
default:
message =
whatsappReminderTemplate(false, action, evt.startTime, evt.title, timeZone, attendeeName, name) ||
message;
whatsappReminderTemplate(
false,
action,
evt.organizer.timeFormat,
evt.startTime,
evt.title,
timeZone,
attendeeName,
name
) || message;
}
// Allows debugging generated whatsapp content without waiting for twilio to send whatsapp messages

View File

@ -19,6 +19,8 @@ import { trpc } from "@calcom/trpc/react";
import type { MultiSelectCheckboxesOptionType as Option } from "@calcom/ui";
import { Alert, Button, Form, showToast, Badge } from "@calcom/ui";
import useMeQuery from "@lib/hooks/useMeQuery";
import LicenseRequired from "../../common/components/LicenseRequired";
import SkeletonLoader from "../components/SkeletonLoaderEdit";
import WorkflowDetailsPage from "../components/WorkflowDetailsPage";
@ -93,6 +95,9 @@ function WorkflowPage() {
const { workflow: workflowId } = router.isReady ? querySchema.parse(router.query) : { workflow: -1 };
const utils = trpc.useContext();
const userQuery = useMeQuery();
const user = userQuery.data;
const {
data: workflow,
isError,
@ -285,11 +290,12 @@ function WorkflowPage() {
<LicenseRequired>
{!isError ? (
<>
{isAllDataLoaded ? (
{isAllDataLoaded && user ? (
<>
<WorkflowDetailsPage
form={form}
workflowId={+workflowId}
user={user}
selectedEventTypes={selectedEventTypes}
setSelectedEventTypes={setSelectedEventTypes}
teamId={workflow ? workflow.teamId || undefined : undefined}

View File

@ -24,6 +24,10 @@ export const getIs24hClockFromLocalStorage = () => {
return is24hFromLocalstorage === "true";
};
export const getTimeFormatStringFromUserTimeFormat = (timeFormat: number | null | undefined) => {
return timeFormat === 24 ? TimeFormat.TWENTY_FOUR_HOUR : TimeFormat.TWELVE_HOUR;
};
/**
* Retrieves the browsers time format preference, checking local storage first
* for a user set preference. If no preference is found, it will use the browser

View File

@ -8,6 +8,7 @@ import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhoo
import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload";
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
import { getTranslation } from "@calcom/lib/server";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma } from "@calcom/prisma";
import { BookingStatus, MembershipRole, SchedulingType, WebhookTriggerEvents } from "@calcom/prisma/enums";
import type { CalendarEvent } from "@calcom/types/Calendar";
@ -162,6 +163,7 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => {
name: user.name || "Unnamed",
username: user.username || undefined,
timeZone: user.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(user.timeFormat),
language: { translate: tOrganizer, locale: user.locale ?? "en" },
},
attendees: attendeesList,

View File

@ -11,6 +11,7 @@ import {
scheduleWhatsappReminder,
} from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager";
import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma } from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/client";
import { MembershipRole, WorkflowActions, WorkflowMethods } from "@calcom/prisma/enums";
@ -171,6 +172,7 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType
name: booking.user.name || "",
email: booking.user.email,
timeZone: booking.user.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(booking.user.timeFormat),
language: { locale: booking.user.locale || defaultLocale },
}
: { name: "", email: "", timeZone: "", language: { locale: "" } },

View File

@ -2,6 +2,7 @@ import type { Workflow } from "@prisma/client";
import emailReminderTemplate from "@calcom/ee/workflows/lib/reminders/templates/emailReminderTemplate";
import { SENDER_NAME } from "@calcom/lib/constants";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma } from "@calcom/prisma";
import type { PrismaClient } from "@calcom/prisma/client";
import {
@ -65,13 +66,19 @@ export const createHandler = async ({ ctx, input }: CreateOptions) => {
},
});
const renderedEmailTemplate = emailReminderTemplate(
true,
WorkflowActions.EMAIL_ATTENDEE,
getTimeFormatStringFromUserTimeFormat(ctx.user.timeFormat)
);
await ctx.prisma.workflowStep.create({
data: {
stepNumber: 1,
action: WorkflowActions.EMAIL_ATTENDEE,
template: WorkflowTemplates.REMINDER,
reminderBody: emailReminderTemplate(true, WorkflowActions.EMAIL_ATTENDEE).emailBody,
emailSubject: emailReminderTemplate(true, WorkflowActions.EMAIL_ATTENDEE).emailSubject,
reminderBody: renderedEmailTemplate.emailBody,
emailSubject: renderedEmailTemplate.emailSubject,
workflowId: workflow.id,
sender: SENDER_NAME,
numberVerificationPending: false,

View File

@ -15,6 +15,7 @@ import {
} from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager";
import { IS_SELF_HOSTED, SENDER_ID, SENDER_NAME } from "@calcom/lib/constants";
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import type { PrismaClient } from "@calcom/prisma/client";
import { BookingStatus, WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom/prisma/enums";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
@ -280,6 +281,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
name: booking.user.name || "",
email: booking.user.email,
timeZone: booking.user.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(booking.user.timeFormat),
}
: { name: "", email: "", timeZone: "", language: { locale: "" } },
startTime: booking.startTime.toISOString(),
@ -503,6 +505,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
name: booking.user.name || "",
email: booking.user.email,
timeZone: booking.user.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(booking.user.timeFormat),
}
: { name: "", email: "", timeZone: "", language: { locale: "" } },
startTime: booking.startTime.toISOString(),
@ -649,6 +652,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
name: booking.user.name || "",
email: booking.user.email,
timeZone: booking.user.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(booking.user.timeFormat),
language: { locale: booking.user.locale || defaultLocale },
}
: { name: "", email: "", timeZone: "", language: { locale: "" } },