import type { App_RoutingForms_Form, Team } from "@prisma/client"; import Link from "next/link"; import { useEffect, useState } from "react"; import type { UseFormReturn } from "react-hook-form"; import { Controller, useFormContext } from "react-hook-form"; import { ShellMain } from "@calcom/features/shell/Shell"; import useApp from "@calcom/lib/hooks/useApp"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import type { AppGetServerSidePropsContext, AppPrisma, AppSsrInit, AppUser, } from "@calcom/types/AppGetServerSideProps"; import { Alert, Badge, Button, ButtonGroup, Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DropdownMenuSeparator, Form, Meta, SettingsToggle, showToast, TextAreaField, TextField, Tooltip, VerticalDivider, } from "@calcom/ui"; import { ExternalLink, Link as LinkIcon, Download, Code, Trash, MessageCircle, } from "@calcom/ui/components/icon"; import { RoutingPages } from "../lib/RoutingPages"; import { getSerializableForm } from "../lib/getSerializableForm"; import { isFallbackRoute } from "../lib/isFallbackRoute"; import { processRoute } from "../lib/processRoute"; import type { Response, Route, SerializableForm } from "../types/types"; import { FormAction, FormActionsDropdown, FormActionsProvider } from "./FormActions"; import FormInputFields from "./FormInputFields"; import RoutingNavBar from "./RoutingNavBar"; type RoutingForm = SerializableForm; export type RoutingFormWithResponseCount = RoutingForm & { team: { slug: Team["slug"]; name: Team["name"]; } | null; _count: { responses: number; }; }; const Actions = ({ form, mutation, }: { form: RoutingFormWithResponseCount; mutation: { isLoading: boolean; }; }) => { const { t } = useLocale(); const { data: typeformApp } = useApp("typeform"); return (
{typeformApp?.isInstalled ? ( {t("Copy Typeform Redirect Url")} ) : null}
{t("preview")} {t("copy_link_to_form")} {t("download_responses")} {t("embed")} {typeformApp ? ( {t("Copy Typeform Redirect Url")} ) : null} {t("delete")}
); }; type SingleFormComponentProps = { form: RoutingFormWithResponseCount; appUrl: string; Page: React.FC<{ form: RoutingFormWithResponseCount; appUrl: string; hookForm: UseFormReturn; }>; }; function SingleForm({ form, appUrl, Page }: SingleFormComponentProps) { const utils = trpc.useContext(); const { t } = useLocale(); const [isTestPreviewOpen, setIsTestPreviewOpen] = useState(false); const [response, setResponse] = useState({}); const [decidedAction, setDecidedAction] = useState(null); const [skipFirstUpdate, setSkipFirstUpdate] = useState(true); function testRouting() { const action = processRoute({ form, response }); setDecidedAction(action); } const hookForm = useFormContext(); 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 connectedForms = form.connectedForms; return ( <>
{ // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore mutation.mutate({ ...data, }); }}>
{form.name}
{form.team && ( {form.team.name} )} } subtitle={form.description || ""} backPath={`/${appUrl}/forms`} CTA={}>
{ return ( onChange(val)} /> ); }} />
{form.routers.length ? (
Routers

{t("modifications_in_fields_warning")}

{form.routers.map((router) => { return (
{router.name}
); })}
) : null} {connectedForms?.length ? (
{t("connected_forms")}

{t("form_modifications_warning")}

{connectedForms.map((router) => { return (
{router.name}
); })}
) : null}
{form.routes?.every(isFallbackRoute) && ( )} {!form._count?.responses && ( <> )}
{ e.preventDefault(); testRouting(); }}>
{form && }
{decidedAction && (
{t("route_to")}:
{RoutingPages.map((page) => { if (page.value !== decidedAction.type) return null; return (
{page.label}
); })} :{" "} {decidedAction.type === "customPageMessage" ? ( {decidedAction.value} ) : decidedAction.type === "externalRedirectUrl" ? ( {decidedAction.value} ) : ( {decidedAction.value} )}
)}
{ setIsTestPreviewOpen(false); setDecidedAction(null); setResponse({}); }}> {t("close")}
); } export default function SingleFormWrapper({ form: _form, ...props }: SingleFormComponentProps) { const { data: form, isLoading } = trpc.viewer.appRoutingForms.formQuery.useQuery( { id: _form.id }, { initialData: _form, trpc: {}, } ); const { t } = useLocale(); if (isLoading) { // 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 ; } export const getServerSidePropsForSingleFormView = async function getServerSidePropsForSingleFormView( context: AppGetServerSidePropsContext, prisma: AppPrisma, user: AppUser, ssrInit: AppSsrInit ) { const ssr = await ssrInit(context); if (!user) { return { redirect: { permanent: false, destination: "/auth/login", }, }; } const { params } = context; if (!params) { return { notFound: true, }; } const formId = params.appPages[0]; if (!formId || params.appPages.length > 1) { return { notFound: true, }; } const isFormCreateEditAllowed = (await import("../lib/isFormCreateEditAllowed")).isFormCreateEditAllowed; if (!(await isFormCreateEditAllowed({ userId: user.id, formId, targetTeamId: null }))) { return { notFound: true, }; } const form = await prisma.app_RoutingForms_Form.findUnique({ where: { id: formId, }, include: { team: { select: { name: true, slug: true, }, }, _count: { select: { responses: true, }, }, }, }); if (!form) { return { notFound: true, }; } return { props: { trpcState: ssr.dehydrate(), form: await getSerializableForm({ form }), }, }; };