"use client"; import { AnimatePresence, motion } from "framer-motion"; import type { Dispatch, SetStateAction } from "react"; import { useState, useMemo } from "react"; import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; import { WEBSITE_URL } from "@calcom/lib/constants"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; import type { Brand } from "@calcom/types/utils"; import { Button } from "@calcom/ui/components/button"; import { Dialog, DialogContent, DialogHeader, DialogFooter } from "@calcom/ui/components/dialog"; import { showToast } from "@calcom/ui/components/toast"; import { TRPCClientError } from "@trpc/react-query"; import { findMatchingRoute } from "../../lib/processRoute"; import { substituteVariables } from "../../lib/substituteVariables"; import type { SingleFormComponentProps } from "../../types/shared"; import type { RoutingForm, FormResponse, NonRouterRoute } from "../../types/types"; import FormInputFields from "../FormInputFields"; import { ResultsView as Results } from "./ResultSection"; import type { MembersMatchResultType } from "./TeamMembersMatchResult"; import { TeamMembersMatchResult } from "./TeamMembersMatchResult"; export type UptoDateForm = Brand< NonNullable, "UptoDateForm" >; const FormView = ({ form, response, setResponse, areRequiredFieldsFilled, onClose, onSubmit, renderFooter, }: { form: UptoDateForm | RoutingForm; response: FormResponse; setResponse: Dispatch>; areRequiredFieldsFilled: boolean; onClose: () => void; onSubmit: () => void; renderFooter?: (onClose: () => void, onSubmit: () => void, isValid: boolean) => React.ReactNode; }) => { const { t } = useLocale(); return (
{ e.preventDefault(); }}> {form && } {!renderFooter ? (
) : ( renderFooter(onClose, onSubmit, areRequiredFieldsFilled) )}
); }; export const TestForm = ({ form, supportsTeamMembersMatchingLogic, renderFooter, isDialog = false, onClose: onCloseProp, showRRData = false, }: { form: UptoDateForm | RoutingForm; supportsTeamMembersMatchingLogic: boolean; renderFooter?: (onClose: () => void, onSubmit: () => void, isValid: boolean) => React.ReactNode; isDialog?: boolean; onClose?: () => void; showRRData?: boolean; }) => { const { t } = useLocale(); const [response, setResponse] = useState({}); const [chosenRoute, setChosenRoute] = useState(null); const searchParams = useCompatSearchParams(); const [membersMatchResult, setMembersMatchResult] = useState(null); const [showResults, setShowResults] = useState(false); const [formKey, setFormKey] = useState(0); const orgBranding = useOrgBranding(); const embedLink = `forms/${form.id}`; const formLink = `${orgBranding?.fullDomain ?? WEBSITE_URL}/${embedLink}`; const areRequiredFieldsFilled = useMemo(() => { if (!form.fields) return true; const requiredFields = form.fields.filter((field) => field.required); if (!requiredFields.length) return true; return requiredFields.every((field) => { const fieldResponse = response[field.id]; if (!fieldResponse) return false; const value = fieldResponse.value; if (Array.isArray(value)) { return value.length > 0; } return value !== undefined && value !== null && value !== ""; }); }, [form.fields, response]); const resetMembersMatchResult = () => { setMembersMatchResult(null); setShowResults(false); }; function testRouting() { const route = findMatchingRoute({ form, response }); // Create a copy of the route with substituted variables for display let displayRoute = route; if (route && form.fields && route.action.type === "eventTypeRedirectUrl") { const substitutedUrl = substituteVariables(route.action.value, response, form.fields); displayRoute = { ...route, action: { ...route.action, value: substitutedUrl, }, }; } setChosenRoute(displayRoute || null); setShowResults(true); if (!route) return; // Custom Event Type Redirect URL has eventTypeId=0. Also, findTeamMembersMatchingAttributeLogicMutation can't work without eventTypeId if (supportsTeamMembersMatchingLogic && route.action.eventTypeId) { findTeamMembersMatchingAttributeLogicMutation.mutate({ formId: form.id, response, route, isPreview: true, _enablePerf: searchParams.get("enablePerf") === "true", }); } } const onClose = () => { setChosenRoute(null); setResponse({}); setShowResults(false); onCloseProp?.(); }; function resetForm() { setChosenRoute(null); setResponse({}); setShowResults(false); // This is a hack to force the form to reset RAQB doesnt seem to be resetting the form when the form is re-rendered setFormKey((prevKey) => prevKey + 1); } const findTeamMembersMatchingAttributeLogicMutation = trpc.viewer.routingForms.findTeamMembersMatchingAttributeLogicOfRoute.useMutation({ onSuccess(data) { setMembersMatchResult({ isUsingAttributeWeights: data.isUsingAttributeWeights, eventTypeRedirectUrl: data.eventTypeRedirectUrl, contactOwnerEmail: data.contactOwnerEmail, teamMembersMatchingAttributeLogic: data.result ? data.result.users : data.result, perUserData: data.result ? data.result.perUserData : null, checkedFallback: data.checkedFallback, mainWarnings: data.mainWarnings, fallbackWarnings: data.fallbackWarnings, }); }, onError(e) { if (e instanceof TRPCClientError) { showToast(e.message, "error"); } else { showToast(t("something_went_wrong"), "error"); } }, }); return (
{!showResults ? ( <> {isDialog ? ( ) : !showRRData ? (

{t("preview")}

) : ( <> )} { resetMembersMatchResult(); testRouting(); }} renderFooter={renderFooter} /> ) : ( <> {isDialog ? ( ) : !showRRData ? (

{t("results")}

) : ( <> )} {showRRData ? ( <> ) : ( )} {isDialog && ( )} )}
); }; export const TestFormRenderer = ({ testForm, isTestPreviewOpen, setIsTestPreviewOpen, isMobile, }: { testForm: UptoDateForm; isTestPreviewOpen: boolean; setIsTestPreviewOpen: (value: boolean) => void; isMobile: boolean; }) => { const { t } = useLocale(); const isSubTeamForm = !!testForm.team?.parentId; if (!isMobile) { if (isTestPreviewOpen) { return (
setIsTestPreviewOpen(false)} />
); } } return ( ( )} /> ); };