"use client"; import { AnimatePresence, motion } from "framer-motion"; import { useEffect, useState } from "react"; import { useFormContext } from "react-hook-form"; import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import classNames from "@calcom/ui/classNames"; import { Form } from "@calcom/ui/components/form"; import { showToast } from "@calcom/ui/components/toast"; import type { SingleFormComponentProps } from "../types/shared"; import type { RoutingFormWithResponseCount } from "../types/types"; import type { NewFormDialogState } from "./FormActions"; import { FormActionsProvider } from "./FormActions"; import { InfoLostWarningDialog } from "./InfoLostWarningDialog"; import { Header } from "./_components/Header"; import { TestFormRenderer, type UptoDateForm } from "./_components/TestForm"; const BREAKPOINTS = { sm: 640, md: 768, lg: 1024, xl: 1280, "2xl": 1536, } as const; type Breakpoint = keyof typeof BREAKPOINTS; type BreakpointState = Record; function useBreakPoints() { const [breakpoints, setBreakpoints] = useState({ sm: false, md: false, lg: false, xl: false, "2xl": false, }); useEffect(() => { const updateBreakpoints = () => { const width = window.innerWidth; setBreakpoints({ sm: width >= BREAKPOINTS.sm, md: width >= BREAKPOINTS.md, lg: width >= BREAKPOINTS.lg, xl: width >= BREAKPOINTS.xl, "2xl": width >= BREAKPOINTS["2xl"], }); }; // Initial check updateBreakpoints(); // Add resize listener window.addEventListener("resize", updateBreakpoints); return () => window.removeEventListener("resize", updateBreakpoints); }, []); return { ...breakpoints, // Convenience properties isMobile: !breakpoints.md, isTablet: breakpoints.md && !breakpoints.lg, isDesktop: breakpoints.lg, }; } /** * It has the the ongoing changes in the form along with enrichedWithUserProfileForm specific data. * So, it can be used to test the form in the test preview dialog without saving the changes even. */ function SingleForm({ form, appUrl, Page, enrichedWithUserProfileForm, permissions, }: SingleFormComponentProps) { const utils = trpc.useUtils(); const { t } = useLocale(); const [newFormDialogState, setNewFormDialogState] = useState(null); const [isTestPreviewOpen, setIsTestPreviewOpen] = useState(false); const [skipFirstUpdate, setSkipFirstUpdate] = useState(true); const [showInfoLostDialog, setShowInfoLostDialog] = useState(false); const hookForm = useFormContext(); const { isDesktop } = useBreakPoints(); useEffect(() => { // The first time a tab is opened, the hookForm copies the form data (saved version, from the backend), // and then it is considered the source of truth. // There are two events we need to overwrite the hookForm data with the form data coming from the server. // 1 - When we change the edited form. // 2 - When the form is saved elsewhere (such as in another browser tab) // In the second case. We skipped the first execution of useEffect to differentiate a tab change from a form change, // because each time a tab changes, a new component is created and another useEffect is executed. // An update from the form always occurs after the first useEffect execution. if (Object.keys(hookForm.getValues()).length === 0 || hookForm.getValues().id !== form.id) { hookForm.reset(form); } if (skipFirstUpdate) { setSkipFirstUpdate(false); } else { hookForm.reset(form); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [form]); const mutation = trpc.viewer.appRoutingForms.formMutation.useMutation({ onSuccess() { showToast(t("form_updated_successfully"), "success"); }, onError(e) { if (e.message) { showToast(e.message, "error"); return; } showToast(`Something went wrong`, "error"); }, onSettled() { utils.viewer.appRoutingForms.formQuery.invalidate({ id: form.id }); }, }); const uptoDateForm = { ...hookForm.getValues(), routes: hookForm.watch("routes"), user: enrichedWithUserProfileForm.user, team: enrichedWithUserProfileForm.team, nonOrgUsername: enrichedWithUserProfileForm.nonOrgUsername, nonOrgTeamslug: enrichedWithUserProfileForm.nonOrgTeamslug, userOrigin: enrichedWithUserProfileForm.userOrigin, teamOrigin: enrichedWithUserProfileForm.teamOrigin, } as UptoDateForm; const handleSubmit = (data: RoutingFormWithResponseCount) => { mutation.mutate({ ...data, }); }; return ( <>
{isDesktop ? ( ) : (
)} {isTestPreviewOpen && isDesktop ? ( ) : isTestPreviewOpen ? (
) : null}
{showInfoLostDialog && ( { mutation.mutate({ ...hookForm.getValues(), }); }} goToRoute={`${appUrl}/route-builder/${form?.id}`} isOpenInfoLostDialog={showInfoLostDialog} setIsOpenInfoLostDialog={setShowInfoLostDialog} /> )} ); } export default function SingleFormWrapper({ form: _form, ...props }: SingleFormComponentProps) { const { data: form, isPending } = trpc.viewer.appRoutingForms.formQuery.useQuery( { id: _form.id }, { initialData: _form, trpc: {}, } ); const { t } = useLocale(); if (isPending) { // It shouldn't be possible because we are passing the data from SSR to it as initialData. So, no need for skeleton here return null; } if (!form) { throw new Error(t("something_went_wrong")); } return ( ); }