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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import dayjs from "@calcom/dayjs"; import dayjs from "@calcom/dayjs";
import logger from "@calcom/lib/logger"; import logger from "@calcom/lib/logger";
import type { TimeFormat } from "@calcom/lib/timeFormat";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client"; import type { Prisma } from "@calcom/prisma/client";
import type { TimeUnit } from "@calcom/prisma/enums"; import type { TimeUnit } from "@calcom/prisma/enums";
@ -29,6 +30,7 @@ export type BookingInfo = {
name: string; name: string;
email: string; email: string;
timeZone: string; timeZone: string;
timeFormat?: TimeFormat;
username?: string; username?: string;
}; };
eventType: { eventType: {
@ -115,11 +117,20 @@ export const scheduleSMSReminder = async (
cancelLink: `/booking/${evt.uid}?cancel=true`, cancelLink: `/booking/${evt.uid}?cancel=true`,
rescheduleLink: `/${evt.organizer.username}/${evt.eventType.slug}?rescheduleUid=${evt.uid}`, 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; message = customMessage.text;
} else if (template === WorkflowTemplates.REMINDER) { } else if (template === WorkflowTemplates.REMINDER) {
message = 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 // 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 type { Dayjs } from "@calcom/dayjs";
import dayjs from "@calcom/dayjs"; import dayjs from "@calcom/dayjs";
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants"; import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
import { TimeFormat } from "@calcom/lib/timeFormat";
import type { CalEventResponses } from "@calcom/types/Calendar"; import type { CalEventResponses } from "@calcom/types/Calendar";
export type VariablesType = { export type VariablesType = {
@ -24,6 +25,7 @@ const customTemplate = (
text: string, text: string,
variables: VariablesType, variables: VariablesType,
locale: string, locale: string,
timeFormat?: TimeFormat,
isBrandingDisabled?: boolean isBrandingDisabled?: boolean
) => { ) => {
const translatedDate = new Intl.DateTimeFormat(locale, { const translatedDate = new Intl.DateTimeFormat(locale, {
@ -42,6 +44,8 @@ const customTemplate = (
const cancelLink = variables.cancelLink ? `${WEBAPP_URL}${variables.cancelLink}` : ""; const cancelLink = variables.cancelLink ? `${WEBAPP_URL}${variables.cancelLink}` : "";
const rescheduleLink = variables.rescheduleLink ? `${WEBAPP_URL}${variables.rescheduleLink}` : ""; const rescheduleLink = variables.rescheduleLink ? `${WEBAPP_URL}${variables.rescheduleLink}` : "";
const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
let dynamicText = text let dynamicText = text
.replaceAll("{EVENT_NAME}", variables.eventName || "") .replaceAll("{EVENT_NAME}", variables.eventName || "")
.replaceAll("{ORGANIZER}", variables.organizerName || "") .replaceAll("{ORGANIZER}", variables.organizerName || "")
@ -49,9 +53,9 @@ const customTemplate = (
.replaceAll("{ORGANIZER_NAME}", variables.organizerName || "") //old variable names .replaceAll("{ORGANIZER_NAME}", variables.organizerName || "") //old variable names
.replaceAll("{ATTENDEE_NAME}", variables.attendeeName || "") //old variable names .replaceAll("{ATTENDEE_NAME}", variables.attendeeName || "") //old variable names
.replaceAll("{EVENT_DATE}", translatedDate) .replaceAll("{EVENT_DATE}", translatedDate)
.replaceAll("{EVENT_TIME}", variables.eventDate?.format("H:mmA") || "") .replaceAll("{EVENT_TIME}", variables.eventDate?.format(currentTimeFormat) || "")
.replaceAll("{START_TIME}", variables.eventDate?.format("H:mmA") || "") .replaceAll("{START_TIME}", variables.eventDate?.format(currentTimeFormat) || "")
.replaceAll("{EVENT_END_TIME}", variables.eventEndTime?.format("H:mmA") || "") .replaceAll("{EVENT_END_TIME}", variables.eventEndTime?.format(currentTimeFormat) || "")
.replaceAll("{LOCATION}", locationString) .replaceAll("{LOCATION}", locationString)
.replaceAll("{ADDITIONAL_NOTES}", variables.additionalNotes || "") .replaceAll("{ADDITIONAL_NOTES}", variables.additionalNotes || "")
.replaceAll("{ATTENDEE_EMAIL}", variables.attendeeEmail || "") .replaceAll("{ATTENDEE_EMAIL}", variables.attendeeEmail || "")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,8 @@ import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import * as twilio from "./smsProviders/twilioProvider"; import * as twilio from "./smsProviders/twilioProvider";
import { BookingInfo, deleteScheduledSMSReminder, timeUnitLowerCase } from "./smsReminderManager"; import type { BookingInfo, timeUnitLowerCase } from "./smsReminderManager";
import { deleteScheduledSMSReminder } from "./smsReminderManager";
import { import {
whatsappEventCancelledTemplate, whatsappEventCancelledTemplate,
whatsappEventCompletedTemplate, whatsappEventCompletedTemplate,
@ -68,14 +69,23 @@ export const scheduleWhatsappReminder = async (
switch (template) { switch (template) {
case WorkflowTemplates.REMINDER: case WorkflowTemplates.REMINDER:
message = message =
whatsappReminderTemplate(false, action, evt.startTime, evt.title, timeZone, attendeeName, name) || whatsappReminderTemplate(
message; false,
action,
evt.organizer.timeFormat,
evt.startTime,
evt.title,
timeZone,
attendeeName,
name
) || message;
break; break;
case WorkflowTemplates.CANCELLED: case WorkflowTemplates.CANCELLED:
message = message =
whatsappEventCancelledTemplate( whatsappEventCancelledTemplate(
false, false,
action, action,
evt.organizer.timeFormat,
evt.startTime, evt.startTime,
evt.title, evt.title,
timeZone, timeZone,
@ -88,6 +98,7 @@ export const scheduleWhatsappReminder = async (
whatsappEventRescheduledTemplate( whatsappEventRescheduledTemplate(
false, false,
action, action,
evt.organizer.timeFormat,
evt.startTime, evt.startTime,
evt.title, evt.title,
timeZone, timeZone,
@ -100,6 +111,7 @@ export const scheduleWhatsappReminder = async (
whatsappEventCompletedTemplate( whatsappEventCompletedTemplate(
false, false,
action, action,
evt.organizer.timeFormat,
evt.startTime, evt.startTime,
evt.title, evt.title,
timeZone, timeZone,
@ -109,8 +121,16 @@ export const scheduleWhatsappReminder = async (
break; break;
default: default:
message = message =
whatsappReminderTemplate(false, action, evt.startTime, evt.title, timeZone, attendeeName, name) || whatsappReminderTemplate(
message; 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 // 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 type { MultiSelectCheckboxesOptionType as Option } from "@calcom/ui";
import { Alert, Button, Form, showToast, Badge } 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 LicenseRequired from "../../common/components/LicenseRequired";
import SkeletonLoader from "../components/SkeletonLoaderEdit"; import SkeletonLoader from "../components/SkeletonLoaderEdit";
import WorkflowDetailsPage from "../components/WorkflowDetailsPage"; import WorkflowDetailsPage from "../components/WorkflowDetailsPage";
@ -93,6 +95,9 @@ function WorkflowPage() {
const { workflow: workflowId } = router.isReady ? querySchema.parse(router.query) : { workflow: -1 }; const { workflow: workflowId } = router.isReady ? querySchema.parse(router.query) : { workflow: -1 };
const utils = trpc.useContext(); const utils = trpc.useContext();
const userQuery = useMeQuery();
const user = userQuery.data;
const { const {
data: workflow, data: workflow,
isError, isError,
@ -285,11 +290,12 @@ function WorkflowPage() {
<LicenseRequired> <LicenseRequired>
{!isError ? ( {!isError ? (
<> <>
{isAllDataLoaded ? ( {isAllDataLoaded && user ? (
<> <>
<WorkflowDetailsPage <WorkflowDetailsPage
form={form} form={form}
workflowId={+workflowId} workflowId={+workflowId}
user={user}
selectedEventTypes={selectedEventTypes} selectedEventTypes={selectedEventTypes}
setSelectedEventTypes={setSelectedEventTypes} setSelectedEventTypes={setSelectedEventTypes}
teamId={workflow ? workflow.teamId || undefined : undefined} teamId={workflow ? workflow.teamId || undefined : undefined}

View File

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

View File

@ -11,6 +11,7 @@ import {
scheduleWhatsappReminder, scheduleWhatsappReminder,
} from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager"; } from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager";
import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants"; import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma } from "@calcom/prisma"; import { prisma } from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/client"; import { BookingStatus } from "@calcom/prisma/client";
import { MembershipRole, WorkflowActions, WorkflowMethods } from "@calcom/prisma/enums"; import { MembershipRole, WorkflowActions, WorkflowMethods } from "@calcom/prisma/enums";
@ -171,6 +172,7 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType
name: booking.user.name || "", name: booking.user.name || "",
email: booking.user.email, email: booking.user.email,
timeZone: booking.user.timeZone, timeZone: booking.user.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(booking.user.timeFormat),
language: { locale: booking.user.locale || defaultLocale }, language: { locale: booking.user.locale || defaultLocale },
} }
: { name: "", email: "", timeZone: "", language: { locale: "" } }, : { 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 emailReminderTemplate from "@calcom/ee/workflows/lib/reminders/templates/emailReminderTemplate";
import { SENDER_NAME } from "@calcom/lib/constants"; import { SENDER_NAME } from "@calcom/lib/constants";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma } from "@calcom/prisma"; import { prisma } from "@calcom/prisma";
import type { PrismaClient } from "@calcom/prisma/client"; import type { PrismaClient } from "@calcom/prisma/client";
import { 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({ await ctx.prisma.workflowStep.create({
data: { data: {
stepNumber: 1, stepNumber: 1,
action: WorkflowActions.EMAIL_ATTENDEE, action: WorkflowActions.EMAIL_ATTENDEE,
template: WorkflowTemplates.REMINDER, template: WorkflowTemplates.REMINDER,
reminderBody: emailReminderTemplate(true, WorkflowActions.EMAIL_ATTENDEE).emailBody, reminderBody: renderedEmailTemplate.emailBody,
emailSubject: emailReminderTemplate(true, WorkflowActions.EMAIL_ATTENDEE).emailSubject, emailSubject: renderedEmailTemplate.emailSubject,
workflowId: workflow.id, workflowId: workflow.id,
sender: SENDER_NAME, sender: SENDER_NAME,
numberVerificationPending: false, numberVerificationPending: false,

View File

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