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
This commit is contained in:
Hariom Balhara
2024-10-09 13:24:45 +05:30
committed by GitHub
parent c80daf43b8
commit d9aab9088d
9 changed files with 56 additions and 11 deletions
+2 -2
View File
@@ -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*",
@@ -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<Parameters<typeof getAbsoluteEventTypeRedirectUrl>[0], "isEmbed">
) {
return getAbsoluteEventTypeRedirectUrl({ ...args });
}
@@ -14,7 +14,7 @@ export default function Router({ form, message }: inferSSRProps<typeof getServer
</Head>
<div className="mx-auto my-0 max-w-3xl md:my-24">
<div className="w-full max-w-4xl ltr:mr-2 rtl:ml-2">
<div className="bg-default -mx-4 rounded-sm border border-neutral-200 p-4 py-6 sm:mx-0 sm:px-8">
<div className="text-default bg-default -mx-4 rounded-sm border border-neutral-200 p-4 py-6 sm:mx-0 sm:px-8">
<div>{message}</div>
</div>
</div>
@@ -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",
},
};
};
@@ -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") {
@@ -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 }) => {
+3 -1
View File
@@ -311,7 +311,9 @@
data-cal-link="forms/948ae412-d995-4865-875a-48302588de03">
Book through Routing Form [Dark Theme]
</button>
<button data-cal-namespace="routingFormHeadlessRouter" data-cal-link="router?form=948ae412-d995-4865-875a-48302588de03&Test field=event-routing" data-cal-config='{"flag.coep": "true"}'>
Book through Headless Router
</button>
<h2>Popup Examples - Child Element Target Tests</h2>
<a style="display: block;" data-cal-namespace="childElementTarget" href="javascript:void(0)" data-cal-link="free/30min" data-cal-config='{"flag.coep": "true"}'>
I am Anchor
+5
View File
@@ -419,6 +419,11 @@ Cal("init", "routingFormAuto", {
origin: origin,
});
Cal("init", "routingFormHeadlessRouter", {
debug: true,
origin: origin,
});
Cal.ns.routingFormAuto("on", {
action: "routed",
callback: (e) => {
+3 -3
View File
@@ -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"],