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

@ -22,18 +22,18 @@ Any other relevant information. For example, why do you consider this a bug and
### Actual Results
- What's happening right now that is different from what is expected
- What's happening right now that is different from what is expected
### Expected Results
- This is an ideal result that the system should get after the tests are performed
- This is an ideal result that the system should get after the tests are performed
### Technical details
- Browser version, screen recording, console logs, network requests: You can make a recording with [Bird Eats Bug](https://birdeatsbug.com/).
- Node.js version
- Anything else that you think could be an issue.
- Browser version, screen recording, console logs, network requests: You can make a recording with [Bird Eats Bug](https://birdeatsbug.com/).
- Node.js version
- Anything else that you think could be an issue.
### Evidence
- How was this tested? This is quite mandatory in terms of bugs. Providing evidence of your testing with screenshots or/and videos is an amazing way to prove the bug and a troubleshooting chance to find the solution.
- How was this tested? This is quite mandatory in terms of bugs. Providing evidence of your testing with screenshots or/and videos is an amazing way to prove the bug and a troubleshooting chance to find the solution.

View File

@ -11,31 +11,32 @@ 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.
- If there is a requirement document, please, share it here.
- If there is ab UI/UX design document, please, share it here.
## Type of change
<!-- Please delete bullets that are not relevant. -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Chore (refactoring code, technical debt, workflow improvements)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Chore (refactoring code, technical debt, workflow improvements)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
## How should this be tested?
<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. Write details that help to start the tests -->
- Are there environment variables that should be set?
- What are the minimal test data to have?
- What is expected (happy path) to have (input and output)?
- Any other important info that could help to test that PR
- Are there environment variables that should be set?
- What are the minimal test data to have?
- What is expected (happy path) to have (input and output)?
- Any other important info that could help to test that PR
## Mandatory Tasks
- [ ] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
- [ ] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
## Checklist

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

@ -7,7 +7,7 @@
<a href="https://cal.com/docs/enterprise-features/api#api-server-specifications">Read the API docs</a>
</div>
# Commercial Cal.com Public API
# Commercial Cal.com Public API
Welcome to the Public API ("/apps/api") of the Cal.com.

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,9 +120,9 @@ const BookerComponent = ({
layout !== _layout
) {
const validLayout = bookerLayouts.enabledLayouts.find((userLayout) => userLayout === layout);
validLayout && setLayout(validLayout)
validLayout && setLayout(validLayout);
}
}, [bookerLayouts, validateLayout, setLayout,_layout]);
}, [bookerLayouts, validateLayout, setLayout, _layout]);
useEffect(() => {
if (event.isLoading) return setBookerState("loading");
@ -302,4 +302,4 @@ export const Booker = (props: BookerProps) => {
<BookerComponent {...props} />
</LazyMotion>
);
};
};

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")}>
@ -44,13 +44,15 @@ export const EventLocations = ({ event }: { event: PublicEvent }) => {
<li key={`${location.type}-${index}`} className="mt-1">
<div className="flex flex-row items-center">
<img
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]" : ""
)}
alt={`${getEventLocationType(location.type)?.label} icon`}
/>
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]"
: ""
)}
alt={`${getEventLocationType(location.type)?.label} icon`}
/>
<span>{getLocationToDisplay(location)}</span>
</div>
</li>

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,43 +593,45 @@ 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">
{isSenderIsNeeded ? (
<>
<div className="pt-4">
<div className="flex">
<Label>{t("sender_id")}</Label>
<Tooltip content={t("sender_id_info")}>
<Info className="ml-2 mr-1 mt-0.5 h-4 w-4 text-gray-500" />
</Tooltip>
{!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">
<div className="flex">
<Label>{t("sender_id")}</Label>
<Tooltip content={t("sender_id_info")}>
<Info className="ml-2 mr-1 mt-0.5 h-4 w-4 text-gray-500" />
</Tooltip>
</div>
<Input
type="text"
placeholder={SENDER_ID}
disabled={props.readOnly}
maxLength={11}
{...form.register(`steps.${step.stepNumber - 1}.sender`)}
/>
</div>
<Input
type="text"
placeholder={SENDER_ID}
disabled={props.readOnly}
maxLength={11}
{...form.register(`steps.${step.stepNumber - 1}.sender`)}
/>
</div>
{form.formState.errors.steps &&
form.formState?.errors?.steps[step.stepNumber - 1]?.sender && (
<p className="mt-1 text-xs text-red-500">{t("sender_id_error_message")}</p>
)}
</>
) : (
<>
<div className="pt-4">
<Label>{t("sender_name")}</Label>
<Input
type="text"
disabled={props.readOnly}
placeholder={SENDER_NAME}
{...form.register(`steps.${step.stepNumber - 1}.senderName`)}
/>
</div>
</>
)}
</div>)}
{form.formState.errors.steps &&
form.formState?.errors?.steps[step.stepNumber - 1]?.sender && (
<p className="mt-1 text-xs text-red-500">{t("sender_id_error_message")}</p>
)}
</>
) : (
<>
<div className="pt-4">
<Label>{t("sender_name")}</Label>
<Input
type="text"
disabled={props.readOnly}
placeholder={SENDER_NAME}
{...form.register(`steps.${step.stepNumber - 1}.senderName`)}
/>
</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,20 +675,17 @@ 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(
`steps.${step.stepNumber - 1}.reminderBody`,
smsReminderTemplate(true,action)
smsReminderTemplate(true, action)
);
} else {
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) {
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) {
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,8 +27,8 @@ 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 = (
@ -27,8 +28,7 @@ export const whatsappEventRescheduledTemplate = (
const templateOne = `Hi${
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) {
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

@ -221,7 +221,7 @@ function WorkflowPage() {
//check if phone number is verified
if (
(step.action === WorkflowActions.SMS_NUMBER || step.action === WorkflowActions.WHATSAPP_NUMBER) &&
(step.action === WorkflowActions.SMS_NUMBER || step.action === WorkflowActions.WHATSAPP_NUMBER) &&
!verifiedNumbers?.find((verifiedNumber) => verifiedNumber.phoneNumber === step.sendTo)
) {
isVerified = false;

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({