fix: formatting

This commit is contained in:
zomars 2023-07-18 13:27:54 -07:00
parent d27ff41682
commit 6b5c8935c5
26 changed files with 286 additions and 217 deletions

View File

@ -11,6 +11,7 @@ Fixes # (issue)
## Requirement/Documentation
<!-- Please provide all documents that are important to understand the reason of that PR. -->
- If there is a requirement document, please, share it here.
- If there is ab UI/UX design document, please, share it here.

View File

@ -90,7 +90,6 @@ That's where Cal.com comes in. Self-hosted or hosted by us. White-label by desig
- [Prisma.io](https://prisma.io/?ref=cal.com)
- [Daily.co](https://go.cal.com/daily)
## Contact us
Meet our sales team for any commercial inquiries.

View File

@ -17,7 +17,7 @@ import {
allowDisablingHostConfirmationEmails,
} from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails";
import { FormBuilder } from "@calcom/features/form-builder/FormBuilder";
import { EditableSchema } from "@calcom/features/form-builder/FormBuilderFieldsSchema"
import { EditableSchema } from "@calcom/features/form-builder/FormBuilderFieldsSchema";
import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector";
import { classNames } from "@calcom/lib";
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
@ -87,7 +87,9 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
return {
...field,
hidden: !enabled,
editable: (!enabled ? "system-but-hidden" : "system-but-optional") as z.infer<typeof EditableSchema>
editable: (!enabled ? "system-but-hidden" : "system-but-optional") as z.infer<
typeof EditableSchema
>,
};
}
return field;

View File

@ -12,6 +12,7 @@ items:
Skiff is a privacy-first, end-to-end encrypted workspace with Mail, Pages, Drive, and Calendar applications. Skiff Calendar integrates with Cal.com, enabling you to schedule appointments, organize events, and use your preferred video conferencing product.
## Features
- **Full workspace** — beyond email and calendar, Skiff enables writing and collaborating on end-to-end encrypted notes or wikis. You can also upload, preview, and share files.
- **Native applications** — Skiff has native macOS, Windows, iOS, and Android applications, ensuring you can stay on top of your schedule from any device.
- **Multiple accounts** — all Skiff products support multiple accounts, or sharing your entire team to collaborate inside one workspace.

View File

@ -23,7 +23,7 @@ import { useBookerStore, useInitializeBookerStore } from "./store";
import type { BookerLayout, BookerProps } from "./types";
import { useEvent } from "./utils/event";
import { validateLayout } from "./utils/layout";
import { getQueryParam } from './utils/query-param';
import { getQueryParam } from "./utils/query-param";
import { useBrandColors } from "./utils/use-brand-colors";
const PoweredBy = dynamic(() => import("@calcom/ee/components/PoweredBy"));
@ -120,7 +120,7 @@ const BookerComponent = ({
layout !== _layout
) {
const validLayout = bookerLayouts.enabledLayouts.find((userLayout) => userLayout === layout);
validLayout && setLayout(validLayout)
validLayout && setLayout(validLayout);
}
}, [bookerLayouts, validateLayout, setLayout, _layout]);

View File

@ -128,7 +128,7 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
if (["week_view", "column_view"].includes(layout) && !get().selectedDate) {
set({ selectedDate: dayjs().format("YYYY-MM-DD") });
}
updateQueryParam("layout", layout)
updateQueryParam("layout", layout);
return set({ layout });
},
selectedDate: getQueryParam("date") || null,

View File

@ -1,8 +1,8 @@
import { getEventLocationType, getTranslatedLocation } from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Tooltip } from "@calcom/ui";
import { MapPin } from "@calcom/ui/components/icon";
import { classNames } from "@calcom/lib";
import type { PublicEvent } from "../../types";
import { EventMetaBlock } from "./Details";
@ -20,7 +20,7 @@ export const EventLocations = ({ event }: { event: PublicEvent }) => {
return translatedLocation;
};
const eventLocationType = getEventLocationType(locations[0].type);
const icon = (locations.length > 1 || !eventLocationType?.iconUrl) ? MapPin : eventLocationType.iconUrl;
const icon = locations.length > 1 || !eventLocationType?.iconUrl ? MapPin : eventLocationType.iconUrl;
return (
<EventMetaBlock icon={icon} isDark={eventLocationType?.iconUrl?.includes("-dark")}>
@ -47,7 +47,9 @@ export const EventLocations = ({ event }: { event: PublicEvent }) => {
src={getEventLocationType(location.type)?.iconUrl}
className={classNames(
"h-3 w-3 opacity-70 ltr:mr-[10px] rtl:ml-[10px] dark:opacity-100 ",
!getEventLocationType(location.type)?.iconUrl?.startsWith("/app-store") ? "dark:invert-[.65]" : ""
!getEventLocationType(location.type)?.iconUrl?.startsWith("/app-store")
? "dark:invert-[.65]"
: ""
)}
alt={`${getEventLocationType(location.type)?.label} icon`}
/>

View File

@ -116,8 +116,7 @@ const PaymentForm = (props: Props) => {
color="minimal"
disabled={!holdAcknowledged || ["processing", "error"].includes(state.status)}
id="cancel"
onClick={() => router.back()}
>
onClick={() => router.back()}>
<span id="button-text">{t("cancel")}</span>
</Button>
<Button

View File

@ -207,8 +207,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
).html;
emailBodyEmpty =
customTemplate(reminder.workflowStep.reminderBody || "", variables, emailLocale).text.length ===
0;
customTemplate(reminder.workflowStep.reminderBody || "", variables, emailLocale).text.length === 0;
} else if (reminder.workflowStep.template === WorkflowTemplates.REMINDER) {
emailContent = emailReminderTemplate(
false,

View File

@ -93,7 +93,7 @@ export const AddActionDialog = (props: IAddActionDialog) => {
setIsPhoneNumberNeeded(true);
setIsSenderIdNeeded(true);
setIsEmailAddressNeeded(false);
form.resetField("senderId", { defaultValue: SENDER_ID })
form.resetField("senderId", { defaultValue: SENDER_ID });
} else if (newValue.value === WorkflowActions.EMAIL_ADDRESS) {
setIsEmailAddressNeeded(true);
setIsSenderIdNeeded(false);
@ -102,7 +102,7 @@ export const AddActionDialog = (props: IAddActionDialog) => {
setIsSenderIdNeeded(true);
setIsEmailAddressNeeded(false);
setIsPhoneNumberNeeded(false);
form.resetField("senderId", { defaultValue: SENDER_ID })
form.resetField("senderId", { defaultValue: SENDER_ID });
} else if (newValue.value === WorkflowActions.WHATSAPP_NUMBER) {
setIsSenderIdNeeded(false);
setIsPhoneNumberNeeded(true);
@ -123,17 +123,16 @@ export const AddActionDialog = (props: IAddActionDialog) => {
const canRequirePhoneNumber = (workflowStep: string) => {
return (
WorkflowActions.SMS_ATTENDEE === workflowStep ||
WorkflowActions.WHATSAPP_ATTENDEE === workflowStep
)
}
WorkflowActions.SMS_ATTENDEE === workflowStep || WorkflowActions.WHATSAPP_ATTENDEE === workflowStep
);
};
const showSender = (action: string) => {
return !isSenderIdNeeded && !(
WorkflowActions.WHATSAPP_NUMBER === action ||
WorkflowActions.WHATSAPP_ATTENDEE === action
)
}
return (
!isSenderIdNeeded &&
!(WorkflowActions.WHATSAPP_NUMBER === action || WorkflowActions.WHATSAPP_ATTENDEE === action)
);
};
return (
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
@ -186,7 +185,7 @@ export const AddActionDialog = (props: IAddActionDialog) => {
{isPhoneNumberNeeded && (
<div className="mt-5 space-y-1">
<Label htmlFor="sendTo">{t("phone_number")}</Label>
<div className="mt-1 mb-5">
<div className="mb-5 mt-1">
<Controller
control={form.control}
name="sendTo"
@ -228,7 +227,7 @@ export const AddActionDialog = (props: IAddActionDialog) => {
)}
</>
)}
{showSender(form.getValues('action')) && (
{showSender(form.getValues("action")) && (
<div className="mt-5">
<Label>{t("sender_name")}</Label>
<Input type="text" placeholder={SENDER_NAME} {...form.register(`senderName`)} />

View File

@ -39,8 +39,14 @@ import {
} from "@calcom/ui";
import { ArrowDown, MoreHorizontal, Trash2, HelpCircle, Info } from "@calcom/ui/components/icon";
import {
isAttendeeAction,
isSMSAction,
isSMSOrWhatsappAction,
isWhatsappAction,
getWhatsappTemplateForAction,
} from "../lib/actionHelperFunctions";
import { DYNAMIC_TEXT_VARIABLES } from "../lib/constants";
import { isAttendeeAction, isSMSAction, isSMSOrWhatsappAction, isWhatsappAction, getWhatsappTemplateForAction } from "../lib/actionHelperFunctions";
import { getWorkflowTemplateOptions, getWorkflowTriggerOptions } from "../lib/getOptions";
import emailReminderTemplate from "../lib/reminders/templates/emailReminderTemplate";
import smsReminderTemplate from "../lib/reminders/templates/smsReminderTemplate";
@ -71,14 +77,16 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
const [verificationCode, setVerificationCode] = useState("");
const action = step?.action
const requirePhoneNumber = WorkflowActions.SMS_NUMBER === action || WorkflowActions.WHATSAPP_NUMBER === action;
const action = step?.action;
const requirePhoneNumber =
WorkflowActions.SMS_NUMBER === action || WorkflowActions.WHATSAPP_NUMBER === action;
const [isPhoneNumberNeeded, setIsPhoneNumberNeeded] = useState(requirePhoneNumber);
const [updateTemplate, setUpdateTemplate] = useState(false);
const [firstRender, setFirstRender] = useState(true);
const senderNeeded = step?.action === WorkflowActions.SMS_NUMBER || step?.action === WorkflowActions.SMS_ATTENDEE;
const senderNeeded =
step?.action === WorkflowActions.SMS_NUMBER || step?.action === WorkflowActions.SMS_ATTENDEE;
const [isSenderIsNeeded, setIsSenderIsNeeded] = useState(senderNeeded);
@ -110,20 +118,11 @@ 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));
} else if (isWhatsappAction(action)) {
form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, whatsappReminderTemplate(
true,
action
))
form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, whatsappReminderTemplate(true, action));
} else {
const reminderBodyTemplate = emailReminderTemplate(
true,
action
).emailBody;
const reminderBodyTemplate = emailReminderTemplate(true, action).emailBody;
form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, reminderBodyTemplate);
}
}
@ -135,8 +134,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
form.setValue(`steps.${step.stepNumber - 1}.emailSubject`, subjectTemplate);
}
} else if (step && isWhatsappAction(step.action)) {
const templateBody = getWhatsappTemplateForAction(step.action, step.template)
form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, templateBody)
const templateBody = getWhatsappTemplateForAction(step.action, step.template);
form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, templateBody);
}
const { ref: emailSubjectFormRef, ...restEmailSubjectForm } = step
@ -151,7 +150,11 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
const refReminderBody = useRef<HTMLTextAreaElement | null>(null);
const getNumberVerificationStatus = () => !!step && !!verifiedNumbers.find((number: string) => number === form.getValues(`steps.${step.stepNumber - 1}.sendTo`))
const getNumberVerificationStatus = () =>
!!step &&
!!verifiedNumbers.find(
(number: string) => number === form.getValues(`steps.${step.stepNumber - 1}.sendTo`)
);
const [numberVerified, setNumberVerified] = useState(getNumberVerificationStatus());
@ -236,17 +239,17 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
return (
<>
<div className="flex justify-center">
<div className="w-full border rounded-md min-w-80 bg-default border-subtle p-7">
<div className="min-w-80 bg-default border-subtle w-full rounded-md border p-7">
<div className="flex">
<div className="bg-subtle text-default mt-[3px] flex h-5 w-5 items-center justify-center rounded-full p-1 text-xs font-medium ltr:mr-5 rtl:ml-5">
1
</div>
<div>
<div className="text-base font-bold text-emphasis">{t("trigger")}</div>
<div className="text-sm text-default">{t("when_something_happens")}</div>
<div className="text-emphasis text-base font-bold">{t("trigger")}</div>
<div className="text-default text-sm">{t("when_something_happens")}</div>
</div>
</div>
<div className="border-t border-subtle my-7" />
<div className="border-subtle my-7 border-t" />
<Label>{t("when")}</Label>
<Controller
name="trigger"
@ -291,7 +294,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
<Label>{showTimeSectionAfter ? t("how_long_after") : t("how_long_before")}</Label>
<TimeTimeUnitInput form={form} disabled={props.readOnly} />
{!props.readOnly && (
<div className="flex mt-1 text-gray-500">
<div className="mt-1 flex text-gray-500">
<Info className="mr-1 mt-0.5 h-4 w-4" />
<p className="text-sm">{t("testing_workflow_info_message")}</p>
</div>
@ -318,18 +321,17 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
const canRequirePhoneNumber = (workflowStep: string) => {
return (
WorkflowActions.SMS_ATTENDEE === workflowStep ||
WorkflowActions.WHATSAPP_ATTENDEE === workflowStep
)
}
WorkflowActions.SMS_ATTENDEE === workflowStep || WorkflowActions.WHATSAPP_ATTENDEE === workflowStep
);
};
return (
<>
<div className="flex justify-center my-3">
<div className="my-3 flex justify-center">
<ArrowDown className="text-subtle stroke-[1.5px] text-3xl" />
</div>
<div className="flex justify-center">
<div className="flex w-full border rounded-md min-w-80 bg-default border-subtle p-7">
<div className="min-w-80 bg-default border-subtle flex w-full rounded-md border p-7">
<div className="w-full">
<div className="flex">
<div className="w-full">
@ -338,8 +340,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
{step.stepNumber + 1}
</div>
<div>
<div className="text-base font-bold text-emphasis">{t("action")}</div>
<div className="text-sm text-default">{t("action_is_performed")}</div>
<div className="text-emphasis text-base font-bold">{t("action")}</div>
<div className="text-default text-sm">{t("action_is_performed")}</div>
</div>
</div>
</div>
@ -379,7 +381,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
</div>
)}
</div>
<div className="border-t border-subtle my-7" />
<div className="border-subtle my-7 border-t" />
<div>
<Label>{t("do_this")}</Label>
<Controller
@ -395,16 +397,18 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
if (val) {
const oldValue = form.getValues(`steps.${step.stepNumber - 1}.action`);
const setNumberRequiredConfigs = (phoneNumberIsNeeded: boolean, senderNeeded = true) => {
const setNumberRequiredConfigs = (
phoneNumberIsNeeded: boolean,
senderNeeded = true
) => {
setIsSenderIsNeeded(senderNeeded);
setIsEmailAddressNeeded(false);
setIsPhoneNumberNeeded(phoneNumberIsNeeded);
setNumberVerified(getNumberVerificationStatus());
}
};
if (isSMSAction(val.value)) {
setNumberRequiredConfigs(val.value === WorkflowActions.SMS_NUMBER)
setNumberRequiredConfigs(val.value === WorkflowActions.SMS_NUMBER);
// email action changes to sms action
if (!isSMSAction(oldValue)) {
form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, "");
@ -501,7 +505,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
/>
</div>
{isPhoneNumberNeeded && (
<div className="p-4 pt-0 mt-2 rounded-md bg-muted">
<div className="bg-muted mt-2 rounded-md p-4 pt-0">
<Label className="pt-4">{t("custom_phone_number")}</Label>
<div className="block sm:flex">
<Controller
@ -553,7 +557,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
) : (
!props.readOnly && (
<>
<div className="flex mt-3">
<div className="mt-3 flex">
<TextField
className="rounded-r-none border-r-transparent"
placeholder="Verification code"
@ -589,7 +593,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
)}
</div>
)}
{!isWhatsappAction(form.getValues(`steps.${step.stepNumber - 1}.action`)) && (<div className="p-4 pt-0 mt-2 rounded-md bg-muted">
{!isWhatsappAction(form.getValues(`steps.${step.stepNumber - 1}.action`)) && (
<div className="bg-muted mt-2 rounded-md p-4 pt-0">
{isSenderIsNeeded ? (
<>
<div className="pt-4">
@ -625,7 +630,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
</div>
</>
)}
</div>)}
</div>
)}
{canRequirePhoneNumber(form.getValues(`steps.${step.stepNumber - 1}.action`)) && (
<div className="mt-2">
<Controller
@ -647,7 +653,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
</div>
)}
{isEmailAddressNeeded && (
<div className="p-4 mt-5 rounded-md bg-muted">
<div className="bg-muted mt-5 rounded-md p-4">
<EmailField
required
disabled={props.readOnly}
@ -669,15 +675,12 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
isDisabled={props.readOnly}
onChange={(val) => {
if (val) {
const action = form.getValues(`steps.${step.stepNumber - 1}.action`)
const action = form.getValues(`steps.${step.stepNumber - 1}.action`);
if (val.value === WorkflowTemplates.REMINDER) {
if (isWhatsappAction(action)) {
form.setValue(
`steps.${step.stepNumber - 1}.reminderBody`,
whatsappReminderTemplate(
true,
action
)
whatsappReminderTemplate(true, action)
);
} else if (isSMSAction(action)) {
form.setValue(
@ -696,13 +699,16 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
}
} else {
if (isWhatsappAction(action)) {
form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, getWhatsappTemplateForAction(action, val.value))
form.setValue(
`steps.${step.stepNumber - 1}.reminderBody`,
getWhatsappTemplateForAction(action, val.value)
);
} else {
form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, "");
form.setValue(`steps.${step.stepNumber - 1}.emailSubject`, "");
}
}
field.onChange(val.value)
field.onChange(val.value);
form.setValue(`steps.${step.stepNumber - 1}.template`, val.value);
setUpdateTemplate(!updateTemplate);
}
@ -715,7 +721,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
}}
/>
</div>
<div className="pt-2 mt-2 rounded-md bg-muted md:p-6 md:pt-4">
<div className="bg-muted mt-2 rounded-md pt-2 md:p-6 md:pt-4">
{isEmailSubjectNeeded && (
<div className="mb-6">
<div className="flex items-center">
@ -755,7 +761,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
step.action !== WorkflowActions.SMS_NUMBER ? (
<>
<div className="mb-2 flex items-center pb-[1.5px]">
<Label className="flex-none mb-0 ">
<Label className="mb-0 flex-none ">
{isEmailSubjectNeeded ? t("email_body") : t("text_message")}
</Label>
</div>
@ -795,7 +801,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
reminderBodyFormRef?.(e);
refReminderBody.current = e;
}}
className="h-24 my-0"
className="my-0 h-24"
disabled={props.readOnly}
required
{...restReminderBodyForm}
@ -811,7 +817,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
{!props.readOnly && (
<div className="mt-3 ">
<button type="button" onClick={() => setIsAdditionalInputsDialogOpen(true)}>
<div className="flex mt-2 text-sm text-default">
<div className="text-default mt-2 flex text-sm">
<HelpCircle className="mt-[3px] h-3 w-3 ltr:mr-2 rtl:ml-2" />
<p className="text-left">{t("using_booking_questions_as_variables")}</p>
</div>
@ -923,23 +929,23 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
<DialogContent type="creation" className="sm:max-w-[610px]">
<div className="-m-3 h-[430px] overflow-x-hidden overflow-y-scroll sm:m-0">
<h1 className="w-full text-xl font-semibold ">{t("how_booking_questions_as_variables")}</h1>
<div className="mb-6 rounded-md bg-muted-3 sm:p-4">
<p className="font-medium test-sm">{t("format")}</p>
<ul className="mt-2 ml-5 list-disc text-emphasis">
<div className="bg-muted-3 mb-6 rounded-md sm:p-4">
<p className="test-sm font-medium">{t("format")}</p>
<ul className="text-emphasis ml-5 mt-2 list-disc">
<li>{t("uppercase_for_letters")}</li>
<li>{t("replace_whitespaces_underscores")}</li>
<li>{t("ignore_special_characters_booking_questions")}</li>
</ul>
<div className="mt-4">
<p className="w-full font-medium test-sm">{t("example_1")}</p>
<div className="grid grid-cols-12 mt-2">
<div className="col-span-5 test-sm text-default ltr:mr-2 rtl:ml-2">
<p className="test-sm w-full font-medium">{t("example_1")}</p>
<div className="mt-2 grid grid-cols-12">
<div className="test-sm text-default col-span-5 ltr:mr-2 rtl:ml-2">
{t("booking_question_identifier")}
</div>
<div className="col-span-7 test-sm text-emphasis">{t("company_size")}</div>
<div className="w-full col-span-5 test-sm text-default">{t("variable")}</div>
<div className="test-sm text-emphasis col-span-7">{t("company_size")}</div>
<div className="test-sm text-default col-span-5 w-full">{t("variable")}</div>
<div className="col-span-7 break-words test-sm text-emphasis">
<div className="test-sm text-emphasis col-span-7 break-words">
{" "}
{`{${t("company_size")
.replace(/[^a-zA-Z0-9 ]/g, "")
@ -950,14 +956,14 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
</div>
</div>
<div className="mt-4">
<p className="w-full font-medium test-sm">{t("example_2")}</p>
<div className="grid grid-cols-12 mt-2">
<div className="col-span-5 test-sm text-default ltr:mr-2 rtl:ml-2">
<p className="test-sm w-full font-medium">{t("example_2")}</p>
<div className="mt-2 grid grid-cols-12">
<div className="test-sm text-default col-span-5 ltr:mr-2 rtl:ml-2">
{t("booking_question_identifier")}
</div>
<div className="col-span-7 test-sm text-emphasis">{t("what_help_needed")}</div>
<div className="col-span-5 test-sm text-default">{t("variable")}</div>
<div className="col-span-7 break-words test-sm text-emphasis">
<div className="test-sm text-emphasis col-span-7">{t("what_help_needed")}</div>
<div className="test-sm text-default col-span-5">{t("variable")}</div>
<div className="test-sm text-emphasis col-span-7 break-words">
{" "}
{`{${t("what_help_needed")
.replace(/[^a-zA-Z0-9 ]/g, "")

View File

@ -1,5 +1,11 @@
import { WorkflowActions, WorkflowTemplates, WorkflowTriggerEvents } from "@prisma/client";
import { whatsappEventCancelledTemplate, whatsappEventCompletedTemplate, whatsappEventRescheduledTemplate, whatsappReminderTemplate } from "../lib/reminders/templates/whatsapp";
import {
whatsappEventCancelledTemplate,
whatsappEventCompletedTemplate,
whatsappEventRescheduledTemplate,
whatsappReminderTemplate,
} from "../lib/reminders/templates/whatsapp";
export function isSMSAction(action: WorkflowActions) {
return action === WorkflowActions.SMS_ATTENDEE || action === WorkflowActions.SMS_NUMBER;
@ -10,46 +16,53 @@ export function isWhatsappAction(action: WorkflowActions) {
}
export function isSMSOrWhatsappAction(action: WorkflowActions) {
return isSMSAction(action) || isWhatsappAction(action)
return isSMSAction(action) || isWhatsappAction(action);
}
export function isAttendeeAction(action: WorkflowActions) {
return action === WorkflowActions.SMS_ATTENDEE || action === WorkflowActions.EMAIL_ATTENDEE || action === WorkflowActions.WHATSAPP_ATTENDEE;
return (
action === WorkflowActions.SMS_ATTENDEE ||
action === WorkflowActions.EMAIL_ATTENDEE ||
action === WorkflowActions.WHATSAPP_ATTENDEE
);
}
export function getWhatsappTemplateForTrigger(trigger: WorkflowTriggerEvents): WorkflowTemplates {
switch (trigger) {
case "NEW_EVENT":
case "BEFORE_EVENT":
return WorkflowTemplates.REMINDER
return WorkflowTemplates.REMINDER;
case "AFTER_EVENT":
return WorkflowTemplates.COMPLETED
return WorkflowTemplates.COMPLETED;
case "EVENT_CANCELLED":
return WorkflowTemplates.CANCELLED
return WorkflowTemplates.CANCELLED;
case "RESCHEDULE_EVENT":
return WorkflowTemplates.RESCHEDULED
return WorkflowTemplates.RESCHEDULED;
default:
return WorkflowTemplates.REMINDER
return WorkflowTemplates.REMINDER;
}
}
export function getWhatsappTemplateFunction(template: WorkflowTemplates): typeof whatsappReminderTemplate {
switch (template) {
case "CANCELLED":
return whatsappEventCancelledTemplate
return whatsappEventCancelledTemplate;
case "COMPLETED":
return whatsappEventCompletedTemplate
return whatsappEventCompletedTemplate;
case "RESCHEDULED":
return whatsappEventRescheduledTemplate
return whatsappEventRescheduledTemplate;
case "CUSTOM":
case "REMINDER":
return whatsappReminderTemplate
return whatsappReminderTemplate;
default:
return whatsappReminderTemplate
return whatsappReminderTemplate;
}
}
export function getWhatsappTemplateForAction(action: WorkflowActions, template: WorkflowTemplates): string | null {
const templateFunction = getWhatsappTemplateFunction(template)
return templateFunction(true, action)
export function getWhatsappTemplateForAction(
action: WorkflowActions,
template: WorkflowTemplates
): string | null {
const templateFunction = getWhatsappTemplateFunction(template);
return templateFunction(true, action);
}

View File

@ -25,19 +25,16 @@ export const WORKFLOW_TEMPLATES = [
WorkflowTemplates.REMINDER,
WorkflowTemplates.CANCELLED,
WorkflowTemplates.COMPLETED,
WorkflowTemplates.RESCHEDULED
WorkflowTemplates.RESCHEDULED,
] as const;
export const BASIC_WORKFLOW_TEMPLATES = [
WorkflowTemplates.CUSTOM,
WorkflowTemplates.REMINDER,
] as const;
export const BASIC_WORKFLOW_TEMPLATES = [WorkflowTemplates.CUSTOM, WorkflowTemplates.REMINDER] as const;
export const WHATSAPP_WORKFLOW_TEMPLATES = [
WorkflowTemplates.REMINDER,
WorkflowTemplates.COMPLETED,
WorkflowTemplates.CANCELLED,
WorkflowTemplates.RESCHEDULED
WorkflowTemplates.RESCHEDULED,
] as const;
export const DYNAMIC_TEXT_VARIABLES = [

View File

@ -1,9 +1,9 @@
import type { WorkflowStep } from "@prisma/client";
import { isSMSOrWhatsappAction } from "@calcom/features/ee/workflows/lib/actionHelperFunctions";
import { classNames } from "@calcom/lib";
import { WorkflowActions } from "@calcom/prisma/enums";
import { Zap, Smartphone, Mail, Bell } from "@calcom/ui/components/icon";
import { isSMSOrWhatsappAction } from "@calcom/features/ee/workflows/lib/actionHelperFunctions";
export function getActionIcon(steps: WorkflowStep[], className?: string): JSX.Element {
if (steps.length === 0) {

View File

@ -3,7 +3,13 @@ import type { TFunction } from "next-i18next";
import { WorkflowActions } from "@calcom/prisma/enums";
import { isSMSOrWhatsappAction, isWhatsappAction } from "./actionHelperFunctions";
import { TIME_UNIT, WHATSAPP_WORKFLOW_TEMPLATES, WORKFLOW_ACTIONS, BASIC_WORKFLOW_TEMPLATES, WORKFLOW_TRIGGER_EVENTS } from "./constants";
import {
TIME_UNIT,
WHATSAPP_WORKFLOW_TEMPLATES,
WORKFLOW_ACTIONS,
BASIC_WORKFLOW_TEMPLATES,
WORKFLOW_TRIGGER_EVENTS,
} from "./constants";
export function getWorkflowActionOptions(t: TFunction, isTeamsPlan?: boolean) {
return WORKFLOW_ACTIONS.filter((action) => action !== WorkflowActions.EMAIL_ADDRESS) //removing EMAIL_ADDRESS for now due to abuse episode
@ -33,7 +39,8 @@ export function getWorkflowTimeUnitOptions(t: TFunction) {
}
export function getWorkflowTemplateOptions(t: TFunction, action: WorkflowActions | undefined) {
const TEMPLATES = (action && isWhatsappAction(action)) ? WHATSAPP_WORKFLOW_TEMPLATES : BASIC_WORKFLOW_TEMPLATES;
const TEMPLATES =
action && isWhatsappAction(action) ? WHATSAPP_WORKFLOW_TEMPLATES : BASIC_WORKFLOW_TEMPLATES;
return TEMPLATES.map((template) => {
return { label: t(`${template.toLowerCase()}`), value: template };
}) as { label: string; value: any }[];

View File

@ -1,6 +1,7 @@
import type { Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@prisma/client";
import type { MailData } from "@sendgrid/helpers/classes/mail";
import { isWhatsappAction } from "@calcom/features/ee/workflows/lib/actionHelperFunctions";
import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants";
import { WorkflowTriggerEvents } from "@calcom/prisma/enums";
import { WorkflowActions } from "@calcom/prisma/enums";
@ -10,8 +11,6 @@ import { scheduleEmailReminder } from "./emailReminderManager";
import { scheduleSMSReminder } from "./smsReminderManager";
import { scheduleWhatsappReminder } from "./whatsappReminderManager";
import { isWhatsappAction } from "@calcom/features/ee/workflows/lib/actionHelperFunctions";
type ExtendedCalendarEvent = CalendarEvent & {
metadata?: { videoCallUrl: string | undefined };
eventType: { slug?: string };
@ -113,7 +112,8 @@ export const scheduleWorkflowReminders = async (args: ScheduleWorkflowRemindersA
hideBranding
);
} else if (isWhatsappAction(step.action)) {
const sendTo = step.action === WorkflowActions.WHATSAPP_ATTENDEE ? smsReminderNumber : step.sendTo;
const sendTo =
step.action === WorkflowActions.WHATSAPP_ATTENDEE ? smsReminderNumber : step.sendTo;
await scheduleWhatsappReminder(
evt,
sendTo,
@ -208,7 +208,8 @@ export const sendCancelledReminders = async (args: SendCancelledRemindersArgs) =
hideBranding
);
} else if (isWhatsappAction(step.action)) {
const sendTo = step.action === WorkflowActions.WHATSAPP_ATTENDEE ? smsReminderNumber : step.sendTo;
const sendTo =
step.action === WorkflowActions.WHATSAPP_ATTENDEE ? smsReminderNumber : step.sendTo;
await scheduleWhatsappReminder(
evt,
sendTo,

View File

@ -20,11 +20,11 @@ function assertTwilio(twilio: TwilioClient.Twilio | undefined): asserts twilio i
}
function getDefaultSender(whatsapp = false) {
let defaultSender = process.env.TWILIO_PHONE_NUMBER
let defaultSender = process.env.TWILIO_PHONE_NUMBER;
if (whatsapp) {
defaultSender = `whatsapp:+${process.env.TWILIO_WHATSAPP_PHONE_NUMBER}`
defaultSender = `whatsapp:+${process.env.TWILIO_WHATSAPP_PHONE_NUMBER}`;
}
return defaultSender
return defaultSender;
}
function getSMSNumber(phone: string, whatsapp = false) {
@ -43,7 +43,13 @@ export const sendSMS = async (phoneNumber: string, body: string, sender: string,
return response;
};
export const scheduleSMS = async (phoneNumber: string, body: string, scheduledDate: Date, sender: string, whatsapp = false) => {
export const scheduleSMS = async (
phoneNumber: string,
body: string,
scheduledDate: Date,
sender: string,
whatsapp = false
) => {
assertTwilio(twilio);
const response = await twilio.messages.create({
body: body,

View File

@ -1,4 +1,4 @@
export * from "./whatsappEventCancelledTemplate"
export * from "./whatsappEventCompletedTemplate"
export * from "./whatsappEventReminderTemplate"
export * from "./whatsappEventRescheduledTemplate"
export * from "./whatsappEventCancelledTemplate";
export * from "./whatsappEventCompletedTemplate";
export * from "./whatsappEventReminderTemplate";
export * from "./whatsappEventRescheduledTemplate";

View File

@ -1,6 +1,7 @@
import dayjs from "@calcom/dayjs";
import { WorkflowActions } from "@prisma/client";
import dayjs from "@calcom/dayjs";
export const whatsappEventCancelledTemplate = (
isEditingMode: boolean,
action?: WorkflowActions,
@ -26,7 +27,7 @@ export const whatsappEventCancelledTemplate = (
const templateOne = `Hi${
name ? ` ${name}` : ``
}, your meeting (*${eventName}*) with ${attendee} on ${eventDate} at ${startTime} ${timeZone} has been canceled.`
}, your meeting (*${eventName}*) with ${attendee} on ${eventDate} at ${startTime} ${timeZone} has been canceled.`;
//Twilio supports up to 1024 characters for whatsapp template messages
if (templateOne.length <= 1024) return templateOne;

View File

@ -1,6 +1,7 @@
import dayjs from "@calcom/dayjs";
import { WorkflowActions } from "@prisma/client";
import dayjs from "@calcom/dayjs";
export const whatsappEventCompletedTemplate = (
isEditingMode: boolean,
action?: WorkflowActions,

View File

@ -1,4 +1,5 @@
import { WorkflowActions } from "@prisma/client";
import dayjs from "@calcom/dayjs";
export const whatsappEventRescheduledTemplate = (
@ -28,7 +29,6 @@ export const whatsappEventRescheduledTemplate = (
name ? ` ${name}` : ``
}, your meeting (*${eventName}*) with ${attendee} on ${eventDate} at ${startTime} ${timeZone} has been rescheduled.`;
//Twilio supports up to 1024 characters for whatsapp template messages
if (templateOne.length <= 1024) return templateOne;

View File

@ -2,12 +2,17 @@ import type { TimeUnit } from "@prisma/client";
import { WorkflowTriggerEvents, WorkflowTemplates, WorkflowActions, WorkflowMethods } from "@prisma/client";
import dayjs from "@calcom/dayjs";
import prisma from "@calcom/prisma";
import logger from "@calcom/lib/logger";
import { BookingInfo, deleteScheduledSMSReminder, timeUnitLowerCase } from "./smsReminderManager";
import prisma from "@calcom/prisma";
import * as twilio from "./smsProviders/twilioProvider";
import { whatsappEventCancelledTemplate, whatsappEventCompletedTemplate, whatsappEventRescheduledTemplate, whatsappReminderTemplate } from "./templates/whatsapp";
import { BookingInfo, deleteScheduledSMSReminder, timeUnitLowerCase } from "./smsReminderManager";
import {
whatsappEventCancelledTemplate,
whatsappEventCompletedTemplate,
whatsappEventRescheduledTemplate,
whatsappReminderTemplate,
} from "./templates/whatsapp";
const log = logger.getChildLogger({ prefix: ["[whatsappReminderManager]"] });
@ -55,25 +60,57 @@ export const scheduleWhatsappReminder = async (
}
const name = action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.attendees[0].name : evt.organizer.name;
const attendeeName = action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.organizer.name : evt.attendees[0].name;
const attendeeName =
action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.organizer.name : evt.attendees[0].name;
const timeZone =
action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.attendees[0].timeZone : evt.organizer.timeZone;
switch (template) {
case WorkflowTemplates.REMINDER:
message = whatsappReminderTemplate(false, action, evt.startTime, evt.title, timeZone, attendeeName, name) || message;
message =
whatsappReminderTemplate(false, action, evt.startTime, evt.title, timeZone, attendeeName, name) ||
message;
break;
case WorkflowTemplates.CANCELLED:
message = whatsappEventCancelledTemplate(false, action, evt.startTime, evt.title, timeZone, attendeeName, name) || message;
break
message =
whatsappEventCancelledTemplate(
false,
action,
evt.startTime,
evt.title,
timeZone,
attendeeName,
name
) || message;
break;
case WorkflowTemplates.RESCHEDULED:
message = whatsappEventRescheduledTemplate(false, action, evt.startTime, evt.title, timeZone, attendeeName, name) || message;
message =
whatsappEventRescheduledTemplate(
false,
action,
evt.startTime,
evt.title,
timeZone,
attendeeName,
name
) || message;
break;
case WorkflowTemplates.COMPLETED:
message = whatsappEventCompletedTemplate(false, action, evt.startTime, evt.title, timeZone, attendeeName, name) || message;
break
message =
whatsappEventCompletedTemplate(
false,
action,
evt.startTime,
evt.title,
timeZone,
attendeeName,
name
) || message;
break;
default:
message = whatsappReminderTemplate(false, action, evt.startTime, evt.title, timeZone, attendeeName, name) || message;
message =
whatsappReminderTemplate(false, action, evt.startTime, evt.title, timeZone, attendeeName, name) ||
message;
}
// Allows debugging generated whatsapp content without waiting for twilio to send whatsapp messages

View File

@ -71,9 +71,7 @@ const fieldSchema = z.object({
required: z.boolean().default(false).optional(),
hidden: z.boolean().optional(),
editable: EditableSchema
.default("user")
.optional(),
editable: EditableSchema.default("user").optional(),
sources: z
.array(
z.object({