diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index 8133ae2c6e8178..51958ad44228d6 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -2926,5 +2926,6 @@
"managed_users": "Managed Users",
"managed_users_description": "See all the managed users created by your OAuth client",
"select_oAuth_client": "Select Oauth Client",
+ "on_every_instance": "On every instance",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
diff --git a/packages/app-store/routing-forms/components/RoutingNavBar.tsx b/packages/app-store/routing-forms/components/RoutingNavBar.tsx
index 3c02afd411f001..57276c8fd00edf 100644
--- a/packages/app-store/routing-forms/components/RoutingNavBar.tsx
+++ b/packages/app-store/routing-forms/components/RoutingNavBar.tsx
@@ -38,6 +38,10 @@ export default function RoutingNavBar({
target: "_blank",
href: `${appUrl}/reporting/${form?.id}`,
},
+ {
+ name: "Incomplete Booking",
+ href: `${appUrl}/incomplete-booking/${form?.id}`,
+ },
];
return (
diff --git a/packages/app-store/routing-forms/lib/enabledIncompleteBookingApps.ts b/packages/app-store/routing-forms/lib/enabledIncompleteBookingApps.ts
new file mode 100644
index 00000000000000..5aee6237c13504
--- /dev/null
+++ b/packages/app-store/routing-forms/lib/enabledIncompleteBookingApps.ts
@@ -0,0 +1 @@
+export const enabledIncompleteBookingApps = ["salesforce"];
diff --git a/packages/app-store/routing-forms/lib/incompleteBooking/actionDataSchemas.ts b/packages/app-store/routing-forms/lib/incompleteBooking/actionDataSchemas.ts
new file mode 100644
index 00000000000000..291e8e1d5fc0be
--- /dev/null
+++ b/packages/app-store/routing-forms/lib/incompleteBooking/actionDataSchemas.ts
@@ -0,0 +1,10 @@
+import type { z } from "zod";
+
+import { routingFormIncompleteBookingDataSchema as salesforceRoutingFormIncompleteBookingDataSchema } from "@calcom/app-store/salesforce/zod";
+import { IncompleteBookingActionType } from "@calcom/prisma/enums";
+
+const incompleteBookingActionDataSchemas: Record> = {
+ [IncompleteBookingActionType.SALESFORCE]: salesforceRoutingFormIncompleteBookingDataSchema,
+};
+
+export default incompleteBookingActionDataSchemas;
diff --git a/packages/app-store/routing-forms/lib/incompleteBooking/actionFunctions.ts b/packages/app-store/routing-forms/lib/incompleteBooking/actionFunctions.ts
new file mode 100644
index 00000000000000..7758b68dbacb21
--- /dev/null
+++ b/packages/app-store/routing-forms/lib/incompleteBooking/actionFunctions.ts
@@ -0,0 +1,13 @@
+import type { App_RoutingForms_IncompleteBookingActions } from "@prisma/client";
+
+import { incompleteBookingAction as salesforceIncompleteBookingAction } from "@calcom/app-store/salesforce/lib/routingForm/incompleteBookingAction";
+import { IncompleteBookingActionType } from "@calcom/prisma/enums";
+
+const incompleteBookingActionFunctions: Record<
+ IncompleteBookingActionType,
+ (action: App_RoutingForms_IncompleteBookingActions, email: string) => void
+> = {
+ [IncompleteBookingActionType.SALESFORCE]: salesforceIncompleteBookingAction,
+};
+
+export default incompleteBookingActionFunctions;
diff --git a/packages/app-store/routing-forms/pages/app-routing-server.config.ts b/packages/app-store/routing-forms/pages/app-routing-server.config.ts
index e0a85fdb7512e7..a80ce55813d5e8 100644
--- a/packages/app-store/routing-forms/pages/app-routing-server.config.ts
+++ b/packages/app-store/routing-forms/pages/app-routing-server.config.ts
@@ -10,4 +10,5 @@ export const routingServerSidePropsConfig: Record
"route-builder": getServerSidePropsSingleForm,
"routing-link": getServerSidePropsRoutingLink,
reporting: getServerSidePropsSingleForm,
+ "incomplete-booking": getServerSidePropsSingleForm,
};
diff --git a/packages/app-store/routing-forms/pages/app-routing.config.tsx b/packages/app-store/routing-forms/pages/app-routing.config.tsx
index 65749ef1936970..f54eb9d1bef8a1 100644
--- a/packages/app-store/routing-forms/pages/app-routing.config.tsx
+++ b/packages/app-store/routing-forms/pages/app-routing.config.tsx
@@ -1,6 +1,7 @@
//TODO: Generate this file automatically so that like in Next.js file based routing can work automatically
import * as formEdit from "./form-edit/[...appPages]";
import * as forms from "./forms/[...appPages]";
+import * as IncompleteBooking from "./incomplete-booking/[...appPages]";
import * as LayoutHandler from "./layout-handler/[...appPages]";
import * as Reporting from "./reporting/[...appPages]";
import * as RouteBuilder from "./route-builder/[...appPages]";
@@ -13,6 +14,7 @@ const routingConfig = {
"routing-link": RoutingLink,
reporting: Reporting,
layoutHandler: LayoutHandler,
+ "incomplete-booking": IncompleteBooking,
};
export default routingConfig;
diff --git a/packages/app-store/routing-forms/pages/incomplete-booking/[...appPages].tsx b/packages/app-store/routing-forms/pages/incomplete-booking/[...appPages].tsx
new file mode 100644
index 00000000000000..cda51e3a984831
--- /dev/null
+++ b/packages/app-store/routing-forms/pages/incomplete-booking/[...appPages].tsx
@@ -0,0 +1,329 @@
+import { useState, useEffect } from "react";
+import type z from "zod";
+
+import { WhenToWriteToRecord, SalesforceFieldType } from "@calcom/app-store/salesforce/lib/enums";
+import type { writeToRecordDataSchema as salesforceWriteToRecordDataSchema } from "@calcom/app-store/salesforce/zod";
+import { routingFormIncompleteBookingDataSchema as salesforceRoutingFormIncompleteBookingDataSchema } from "@calcom/app-store/salesforce/zod";
+import Shell from "@calcom/features/shell/Shell";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { IncompleteBookingActionType } from "@calcom/prisma/enums";
+import { trpc } from "@calcom/trpc/react";
+import type { inferSSRProps } from "@calcom/types/inferSSRProps";
+import { Switch, InputField, Button, Select, showToast } from "@calcom/ui";
+
+import SingleForm, {
+ getServerSidePropsForSingleFormView as getServerSideProps,
+} from "../../components/SingleForm";
+import type { RoutingFormWithResponseCount } from "../../components/SingleForm";
+import { enabledIncompleteBookingApps } from "../../lib/enabledIncompleteBookingApps";
+
+function Page({ form }: { form: RoutingFormWithResponseCount }) {
+ const { t } = useLocale();
+ const { data, isLoading } = trpc.viewer.appRoutingForms.getIncompleteBookingSettings.useQuery({
+ formId: form.id,
+ });
+
+ const mutation = trpc.viewer.appRoutingForms.saveIncompleteBookingSettings.useMutation({
+ onSuccess: () => {
+ showToast(t("success"), "success");
+ },
+ onError: (error) => {
+ showToast(t(`error: ${error.message}`), "error");
+ },
+ });
+
+ const [salesforceWriteToRecordObject, setSalesforceWriteToRecordObject] = useState<
+ z.infer
+ >({});
+
+ // Handle just Salesforce for now but need to expand this to other apps
+ const [salesforceActionEnabled, setSalesforceActionEnabled] = useState(false);
+
+ const fieldTypeOptions = [{ label: t("text"), value: SalesforceFieldType.TEXT }];
+
+ const [selectedFieldType, setSelectedFieldType] = useState(fieldTypeOptions[0]);
+
+ const whenToWriteToRecordOptions = [
+ { label: t("on_every_instance"), value: WhenToWriteToRecord.EVERY_BOOKING },
+ { label: t("only_if_field_is_empty"), value: WhenToWriteToRecord.FIELD_EMPTY },
+ ];
+
+ const [selectedWhenToWrite, setSelectedWhenToWrite] = useState(whenToWriteToRecordOptions[0]);
+
+ const [newSalesforceAction, setNewSalesforceAction] = useState({
+ field: "",
+ fieldType: selectedFieldType.value,
+ value: "",
+ whenToWrite: WhenToWriteToRecord.FIELD_EMPTY,
+ });
+
+ const credentialOptions = data?.credentials.map((credential) => ({
+ label: credential.team?.name,
+ value: credential.id,
+ }));
+
+ const [selectedCredential, setSelectedCredential] = useState(
+ Array.isArray(credentialOptions) ? credentialOptions[0] : null
+ );
+
+ useEffect(() => {
+ const salesforceAction = data?.incompleteBookingActions.find(
+ (action) => action.actionType === IncompleteBookingActionType.SALESFORCE
+ );
+
+ if (salesforceAction) {
+ setSalesforceActionEnabled(salesforceAction.enabled);
+
+ const parsedSalesforceActionData = salesforceRoutingFormIncompleteBookingDataSchema.safeParse(
+ salesforceAction.data
+ );
+ if (parsedSalesforceActionData.success) {
+ setSalesforceWriteToRecordObject(parsedSalesforceActionData.data?.writeToRecordObject ?? {});
+ }
+
+ setSelectedCredential(
+ credentialOptions
+ ? credentialOptions.find((option) => option.value === salesforceAction?.credentialId) ??
+ selectedCredential
+ : selectedCredential
+ );
+ }
+ }, [data]);
+
+ if (isLoading) {
+ return
Loading...
;
+ }
+
+ // Check to see if the user has any compatible credentials
+ if (
+ !data?.credentials.some((credential) => enabledIncompleteBookingApps.includes(credential?.appId ?? ""))
+ ) {
+ return