diff --git a/packages/app-store/ee/routing-forms/api/responses/[formId].ts b/packages/app-store/ee/routing-forms/api/responses/[formId].ts index 8688dda8ae..2cdf1846ac 100644 --- a/packages/app-store/ee/routing-forms/api/responses/[formId].ts +++ b/packages/app-store/ee/routing-forms/api/responses/[formId].ts @@ -71,7 +71,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (!form) { throw new Error("Form not found"); } - const serializableForm = getSerializableForm(form, true); + const serializableForm = await getSerializableForm(form, true); res.setHeader("Content-Type", "text/csv; charset=UTF-8"); res.setHeader( "Content-Disposition", diff --git a/packages/app-store/ee/routing-forms/components/FormActions.tsx b/packages/app-store/ee/routing-forms/components/FormActions.tsx index 5eaf239c7d..cb31bfeeca 100644 --- a/packages/app-store/ee/routing-forms/components/FormActions.tsx +++ b/packages/app-store/ee/routing-forms/components/FormActions.tsx @@ -2,6 +2,7 @@ import type { NextRouter } from "next/router"; import { useRouter } from "next/router"; import { createContext, forwardRef, useContext, useState } from "react"; import { useForm } from "react-hook-form"; +import { Controller } from "react-hook-form"; import { v4 as uuidv4 } from "uuid"; import { z } from "zod"; @@ -26,6 +27,7 @@ import { Switch, TextAreaField, TextField, + SettingsToggle, } from "@calcom/ui"; import { EmbedButton, EmbedDialog } from "@components/Embed"; @@ -77,6 +79,7 @@ function NewFormDialog({ appUrl }: { appUrl: string }) { const hookForm = useForm<{ name: string; description: string; + shouldConnect: boolean; }>(); const { action, target } = router.query as z.infer; @@ -97,6 +100,7 @@ function NewFormDialog({ appUrl }: { appUrl: string }) { form={hookForm} handleSubmit={(values) => { const formId = uuidv4(); + mutation.mutate({ id: formId, ...values, @@ -115,6 +119,23 @@ function NewFormDialog({ appUrl }: { appUrl: string }) { placeholder="Form Description" /> + {action === "duplicate" && ( + { + return ( + { + onChange(checked); + }} + /> + ); + }} + /> + )}
+ + {form.routers.length ? ( +
+
+ Routers +
+

+ Modifications in fields and routes of following forms will be reflected in this form. +

+
+ {form.routers.map((router) => { + return ( + + ); + })} +
+
+ ) : null} + + {connectedForms?.length ? ( +
+
+ Connected Forms +
+

+ Following forms would be affected when you modify fields or routes here +

+
+ {connectedForms.map((router) => { + return ( + + ); + })} +
+
+ ) : null} +
} + buttonRaw={ + + } /> - +
); }; @@ -291,7 +319,7 @@ export default function FormEditPage({ } + Page={({ hookForm, form }) => } /> ); } diff --git a/packages/app-store/ee/routing-forms/pages/forms/[...appPages].tsx b/packages/app-store/ee/routing-forms/pages/forms/[...appPages].tsx index 3f26ac4a5a..0507010b75 100644 --- a/packages/app-store/ee/routing-forms/pages/forms/[...appPages].tsx +++ b/packages/app-store/ee/routing-forms/pages/forms/[...appPages].tsx @@ -215,7 +215,10 @@ export const getServerSideProps = async function getServerSideProps( }, }); - const serializableForms = forms.map((form) => getSerializableForm(form)); + const serializableForms = []; + for (const [, form] of Object.entries(forms)) { + serializableForms.push(await getSerializableForm(form)); + } return { props: { diff --git a/packages/app-store/ee/routing-forms/pages/route-builder/[...appPages].tsx b/packages/app-store/ee/routing-forms/pages/route-builder/[...appPages].tsx index f646f58c4e..625f887f66 100644 --- a/packages/app-store/ee/routing-forms/pages/route-builder/[...appPages].tsx +++ b/packages/app-store/ee/routing-forms/pages/route-builder/[...appPages].tsx @@ -1,20 +1,21 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import { App_RoutingForms_Form } from "@prisma/client"; +import Link from "next/link"; import React, { useCallback, useState } from "react"; -import { Builder, Config, Query, Utils as QbUtils } from "react-awesome-query-builder"; +import { Query, Config, Builder, Utils as QbUtils } from "react-awesome-query-builder"; // types -import { BuilderProps, ImmutableTree, JsonTree } from "react-awesome-query-builder"; +import { JsonTree, ImmutableTree, BuilderProps } from "react-awesome-query-builder"; import { trpc } from "@calcom/trpc/react"; import { inferSSRProps } from "@calcom/types/inferSSRProps"; import { - Button, + SelectField, FormCard, - Icon, SelectWithValidation as Select, Shell, TextArea, TextField, + Badge, } from "@calcom/ui"; import SingleForm, { @@ -22,7 +23,9 @@ import SingleForm, { } from "../../components/SingleForm"; import QueryBuilderInitialConfig from "../../components/react-awesome-query-builder/config/config"; import "../../components/react-awesome-query-builder/styles.css"; -import { SerializableForm } from "../../types/types"; +import { createFallbackRoute } from "../../lib/createFallbackRoute"; +import isRouter from "../../lib/isRouter"; +import { GlobalRoute, LocalRoute, SerializableForm, SerializableRoute } from "../../types/types"; import { FieldTypes } from "../form-edit/[...appPages]"; export { getServerSideProps }; @@ -30,8 +33,10 @@ export { getServerSideProps }; type RoutingForm = SerializableForm; const InitialConfig = QueryBuilderInitialConfig; -const hasRules = (route: Route) => +const hasRules = (route: Route) => { + if (isRouter(route)) return false; route.queryValue.children1 && Object.keys(route.queryValue.children1).length; +}; type QueryBuilderUpdatedConfig = typeof QueryBuilderInitialConfig & { fields: Config["fields"] }; export function getQueryBuilderConfig(form: RoutingForm, forReporting = false) { const fields: Record< @@ -49,6 +54,9 @@ export function getQueryBuilderConfig(form: RoutingForm, forReporting = false) { } > = {}; form.fields?.forEach((field) => { + if ("routerField" in field) { + field = field.routerField; + } if (FieldTypes.map((f) => f.value).includes(field.type)) { const optionValues = field.selectText?.trim().split("\n"); const options = optionValues?.map((value) => { @@ -90,7 +98,7 @@ export function getQueryBuilderConfig(form: RoutingForm, forReporting = false) { return config; } -const getEmptyRoute = (): SerializableRoute => { +const getEmptyRoute = (): Exclude => { const uuid = QbUtils.uuid(); return { id: uuid, @@ -102,41 +110,19 @@ const getEmptyRoute = (): SerializableRoute => { }; }; -const createFallbackRoute = (): SerializableRoute => { - const uuid = QbUtils.uuid(); - return { - id: uuid, - isFallback: true, - action: { - type: "customPageMessage", - value: "Thank you for your interest! We will be in touch soon.", - }, - queryValue: { id: uuid, type: "group" }, - }; -}; +type Route = + | (LocalRoute & { + // This is what's persisted + queryValue: JsonTree; + // `queryValue` is parsed to create state + state: { + tree: ImmutableTree; + config: QueryBuilderUpdatedConfig; + }; + }) + | GlobalRoute; -type Route = { - id: string; - isFallback?: boolean; - action: { - type: "customPageMessage" | "externalRedirectUrl" | "eventTypeRedirectUrl"; - value: string; - }; - // This is what's persisted - queryValue: JsonTree; - // `queryValue` is parsed to create state - state: { - tree: ImmutableTree; - config: QueryBuilderUpdatedConfig; - }; -}; - -type SerializableRoute = Pick & { - queryValue: Route["queryValue"]; - isFallback?: Route["isFallback"]; -}; - -export const RoutingPages: { label: string; value: Route["action"]["type"] }[] = [ +export const RoutingPages: { label: string; value: NonNullable["type"] }[] = [ { label: "Custom Page", value: "customPageMessage", @@ -159,6 +145,8 @@ const Route = ({ setRoutes, moveUp, moveDown, + appUrl, + disabled = false, }: { route: Route; routes: Route[]; @@ -167,6 +155,8 @@ const Route = ({ setRoutes: React.Dispatch>; moveUp?: { fn: () => void; check: () => boolean } | null; moveDown?: { fn: () => void; check: () => boolean } | null; + appUrl: string; + disabled?: boolean; }) => { const index = routes.indexOf(route); @@ -202,6 +192,42 @@ const Route = ({ [] ); + if (isRouter(route)) { + return ( +
+ routes.length !== 1, + fn: () => { + const newRoutes = routes.filter((r) => r.id !== route.id); + setRoutes(newRoutes); + }, + }} + label={ +
+ {`Route ${index + 1}`} +
+ } + className="mb-6"> +
+ + + + {route.name} + + + +

+ Fields available in {route.name} will be added to this form. +

+
+
+
+ ); + } + return ( Send Booker to