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
No apps installed that support this feature
; + } + + return ( + <> +
+
+ { + setSalesforceActionEnabled(checked); + }} + /> +
+ + {salesforceActionEnabled ? ( + <> +
+ + {form.team && ( + <> +
+

Credential to use

+ option.value === action.fieldType)} + isDisabled={true} + /> +
+
+ +
+
+ { + if (e) { + setSelectedFieldType(e); + setNewSalesforceAction({ + ...newSalesforceAction, + fieldType: e.value, + }); + } + }} + /> +
+
+ + setNewSalesforceAction({ + ...newSalesforceAction, + value: e.target.value, + }) + } + /> +
+
+