From d9aab9088dd34b76b78f5ac353a8b056f29a58b7 Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Wed, 9 Oct 2024 13:24:45 +0530 Subject: [PATCH] feat: Support headless router as well in embed (#17012) * Support /router/embed path and add Headless router usecase in playground * Fix broken seeded-form testing wrong thing --- apps/web/next.config.js | 4 ++-- .../routing-forms/getEventTypeRedirectUrl.ts | 10 ++++++++ .../pages/router/[...appPages].tsx | 2 +- .../pages/router/getServerSideProps.ts | 23 +++++++++++++++++-- .../pages/routing-link/[...appPages].tsx | 5 ++-- .../playwright/tests/basic.e2e.ts | 8 +++++++ packages/embeds/embed-core/index.html | 4 +++- packages/embeds/embed-core/playground.ts | 5 ++++ packages/prisma/seed-app-store.ts | 6 ++--- 9 files changed, 56 insertions(+), 11 deletions(-) diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 30355a8d3b..ce61936e16 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -291,8 +291,8 @@ const nextConfig = { destination: "/apps/routing-forms/routing-link/:formQuery*", }, { - source: "/router", - destination: "/apps/routing-forms/router", + source: "/router/:path*", + destination: "/apps/routing-forms/router/:path*", }, { source: "/success/:path*", diff --git a/packages/app-store/routing-forms/getEventTypeRedirectUrl.ts b/packages/app-store/routing-forms/getEventTypeRedirectUrl.ts index a1d342d20d..fb82008f34 100644 --- a/packages/app-store/routing-forms/getEventTypeRedirectUrl.ts +++ b/packages/app-store/routing-forms/getEventTypeRedirectUrl.ts @@ -1,4 +1,5 @@ import { CAL_URL } from "@calcom/lib/constants"; +import { Ensure } from "@calcom/types/utils"; function getUserAndEventTypeSlug(eventTypeRedirectUrl: string) { if (eventTypeRedirectUrl.startsWith("/")) { @@ -31,6 +32,7 @@ export function getAbsoluteEventTypeRedirectUrl({ eventTypeRedirectUrl, form, allURLSearchParams, + isEmbed, }: { eventTypeRedirectUrl: string; form: { @@ -56,7 +58,9 @@ export function getAbsoluteEventTypeRedirectUrl({ teamOrigin: string; }; allURLSearchParams: URLSearchParams; + isEmbed?: boolean; }) { + eventTypeRedirectUrl = `${eventTypeRedirectUrl}${isEmbed ? "/embed" : ""}`; // It could be using the old(before migration) username/team-slug or it could be using the new one(after migration) // If it's using the old one, it would work by redirection as long as we use CAL_URL(which is non-org domain) // But if it's using the new one, it has to use the org domain. @@ -86,3 +90,9 @@ export function getAbsoluteEventTypeRedirectUrl({ return `${origin}/${eventTypeRedirectUrl}?${allURLSearchParams}`; } + +export function getAbsoluteEventTypeRedirectUrlWithEmbedSupport( + args: Ensure[0], "isEmbed"> +) { + return getAbsoluteEventTypeRedirectUrl({ ...args }); +} diff --git a/packages/app-store/routing-forms/pages/router/[...appPages].tsx b/packages/app-store/routing-forms/pages/router/[...appPages].tsx index c361190a01..3f9c0b31bf 100644 --- a/packages/app-store/routing-forms/pages/router/[...appPages].tsx +++ b/packages/app-store/routing-forms/pages/router/[...appPages].tsx @@ -14,7 +14,7 @@ export default function Router({ form, message }: inferSSRProps
-
+
{message}
diff --git a/packages/app-store/routing-forms/pages/router/getServerSideProps.ts b/packages/app-store/routing-forms/pages/router/getServerSideProps.ts index de119be8fc..891264be66 100644 --- a/packages/app-store/routing-forms/pages/router/getServerSideProps.ts +++ b/packages/app-store/routing-forms/pages/router/getServerSideProps.ts @@ -7,7 +7,7 @@ import { TRPCError } from "@calcom/trpc/server"; import type { AppGetServerSidePropsContext, AppPrisma } from "@calcom/types/AppGetServerSideProps"; import { enrichFormWithMigrationData } from "../../enrichFormWithMigrationData"; -import { getAbsoluteEventTypeRedirectUrl } from "../../getEventTypeRedirectUrl"; +import { getAbsoluteEventTypeRedirectUrlWithEmbedSupport } from "../../getEventTypeRedirectUrl"; import getFieldIdentifier from "../../lib/getFieldIdentifier"; import { getSerializableForm } from "../../lib/getSerializableForm"; import { findMatchingRoute } from "../../lib/processRoute"; @@ -27,11 +27,24 @@ const querySchema = z }) .catchall(z.string().or(z.array(z.string()))); +function getNamedParams(params: { appPages: string[] } | undefined) { + const [embed] = params?.appPages || []; + return { + // There might not be item at index 0, so explicit assertion is needed + embed: embed as typeof embed | undefined, + }; +} + export const getServerSideProps = async function getServerSideProps( context: AppGetServerSidePropsContext, prisma: AppPrisma ) { const queryParsed = querySchema.safeParse(context.query); + const { embed } = getNamedParams(context.params); + const pageProps = { + isEmbed: !!embed, + }; + if (!queryParsed.success) { log.warn("Error parsing query", queryParsed.error); return { @@ -139,6 +152,7 @@ export const getServerSideProps = async function getServerSideProps( if (e instanceof TRPCError) { return { props: { + ...pageProps, form: serializableForm, message: e.message, }, @@ -150,6 +164,7 @@ export const getServerSideProps = async function getServerSideProps( if (decidedAction.type === "customPageMessage") { return { props: { + ...pageProps, form: serializableForm, message: decidedAction.value, }, @@ -163,7 +178,7 @@ export const getServerSideProps = async function getServerSideProps( return { redirect: { - destination: getAbsoluteEventTypeRedirectUrl({ + destination: getAbsoluteEventTypeRedirectUrlWithEmbedSupport({ eventTypeRedirectUrl: eventTypeUrlWithResolvedVariables, form: serializableForm, allURLSearchParams: getUrlSearchParamsToForward({ @@ -174,6 +189,7 @@ export const getServerSideProps = async function getServerSideProps( // formResponseId is guaranteed to be set because in catch block of trpc request we return from the function and otherwise it would have been set formResponseId: formResponseId!, }), + isEmbed: pageProps.isEmbed, }), permanent: false, }, @@ -187,9 +203,12 @@ export const getServerSideProps = async function getServerSideProps( }; } + // TODO: Consider throwing error here as there is no value of decidedAction.type that would cause the flow to be here return { props: { + ...pageProps, form: serializableForm, + message: "Unhandled type of action", }, }; }; diff --git a/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx b/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx index a6a10d15a7..24eb800425 100644 --- a/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx +++ b/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx @@ -19,7 +19,7 @@ import type { inferSSRProps } from "@calcom/types/inferSSRProps"; import { Button, showToast, useCalcomTheme } from "@calcom/ui"; import FormInputFields from "../../components/FormInputFields"; -import { getAbsoluteEventTypeRedirectUrl } from "../../getEventTypeRedirectUrl"; +import { getAbsoluteEventTypeRedirectUrlWithEmbedSupport } from "../../getEventTypeRedirectUrl"; import getFieldIdentifier from "../../lib/getFieldIdentifier"; import { findMatchingRoute } from "../../lib/processRoute"; import { substituteVariables } from "../../lib/substituteVariables"; @@ -122,10 +122,11 @@ function RoutingForm({ form, profile, ...restProps }: Props) { } else if (decidedAction.type === "eventTypeRedirectUrl") { const eventTypeUrlWithResolvedVariables = substituteVariables(decidedAction.value, response, fields); router.push( - getAbsoluteEventTypeRedirectUrl({ + getAbsoluteEventTypeRedirectUrlWithEmbedSupport({ form, eventTypeRedirectUrl: eventTypeUrlWithResolvedVariables, allURLSearchParams, + isEmbed: !!isEmbed }) ); } else if (decidedAction.type === "externalRedirectUrl") { diff --git a/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts b/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts index 54846ef4f2..fc95160854 100644 --- a/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts +++ b/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts @@ -345,6 +345,14 @@ test.describe("Routing Forms", () => { `/router?form=${routingForm.id}&Test field=doesntmatter&${Identifiers.multiNewFormat}=d1302635-9f12-17b1-9153-c3a854649182` ); await expect(page.locator("text=Multiselect chosen")).toBeVisible({ timeout: 10000 }); + + // Negative test - Ensures that the "No logic" situation where a Route is considered to be always passing isn't hitting + await page.goto(`/router?form=${routingForm.id}&Test field=kuchbhi`); + await page.waitForURL((url) => { + return url.searchParams.get("Test field") === "kuchbhi"; + }); + + expect(page.url()).not.toContain("/pro/30min"); }); test("Routing Link should validate fields", async ({ page, users }) => { diff --git a/packages/embeds/embed-core/index.html b/packages/embeds/embed-core/index.html index 9a9fc5be4c..dc82ab5a23 100644 --- a/packages/embeds/embed-core/index.html +++ b/packages/embeds/embed-core/index.html @@ -311,7 +311,9 @@ data-cal-link="forms/948ae412-d995-4865-875a-48302588de03"> Book through Routing Form [Dark Theme] - +

Popup Examples - Child Element Target Tests

I am Anchor diff --git a/packages/embeds/embed-core/playground.ts b/packages/embeds/embed-core/playground.ts index 810c2883a3..35f4d855f9 100644 --- a/packages/embeds/embed-core/playground.ts +++ b/packages/embeds/embed-core/playground.ts @@ -419,6 +419,11 @@ Cal("init", "routingFormAuto", { origin: origin, }); +Cal("init", "routingFormHeadlessRouter", { + debug: true, + origin: origin, +}); + Cal.ns.routingFormAuto("on", { action: "routed", callback: (e) => { diff --git a/packages/prisma/seed-app-store.ts b/packages/prisma/seed-app-store.ts index e2230f19c8..d5ca9fed3a 100644 --- a/packages/prisma/seed-app-store.ts +++ b/packages/prisma/seed-app-store.ts @@ -54,7 +54,7 @@ async function seedAppData() { "8988bbb8-0123-4456-b89a-b1823f70c5ff": { type: "rule", properties: { - field: "c4296635-9f12-47b1-8153-c3a854649182", + field: "c1296635-9f12-47b1-8153-c3a854649182", value: ["event-routing"], operator: "equal", valueSrc: ["value"], @@ -74,7 +74,7 @@ async function seedAppData() { "b99b8a89-89ab-4cde-b012-31823f718ff5": { type: "rule", properties: { - field: "c4296635-9f12-47b1-8153-c3a854649182", + field: "c1296635-9f12-47b1-8153-c3a854649182", value: ["custom-page"], operator: "equal", valueSrc: ["value"], @@ -94,7 +94,7 @@ async function seedAppData() { "998b9b9a-0123-4456-b89a-b1823f7232b9": { type: "rule", properties: { - field: "c4296635-9f12-47b1-8153-c3a854649182", + field: "c1296635-9f12-47b1-8153-c3a854649182", value: ["external-redirect"], operator: "equal", valueSrc: ["value"],