feat: routing form fields as variables in route (#13519)

* add UI for setting up custom event typ url

* extract variables when processing route

* remove console.log

* fix some UI issues

* code clean up

* add translation

* fix type error

* Update packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

* make variable check case insensitive

* add function

* code clean up

* code clean up

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
This commit is contained in:
Carina Wollendorfer
2024-02-02 19:25:37 -03:00
committed by GitHub
co-authored by CarinaWolli Hariom Balhara
parent 12466fb064
commit 4aa040d96e
4 changed files with 83 additions and 6 deletions
@@ -2242,5 +2242,6 @@
"redirect_to": "Redirect to",
"having_trouble_finding_time": "Having trouble finding a time?",
"show_more": "Show more",
"send_booker_to": "Send Booker to",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -0,0 +1,26 @@
import slugify from "@calcom/lib/slugify";
import type { Response, Route, Field } from "../types/types";
import getFieldIdentifier from "./getFieldIdentifier";
export const substituteVariables = (
routeValue: Route["action"]["value"],
response: Response,
fields: Field[]
) => {
const regex = /\{([^\}]+)\}/g;
const variables: string[] = routeValue.match(regex)?.map((match: string) => match.slice(1, -1)) || [];
let eventTypeUrl = routeValue;
variables.forEach((variable) => {
for (const key in response) {
const identifier = getFieldIdentifier(fields.find((field) => field.id === key));
if (identifier.toLowerCase() === variable.toLowerCase()) {
eventTypeUrl = eventTypeUrl.replace(`{${variable}}`, slugify(response[key].value.toString() || ""));
}
}
});
return eventTypeUrl;
};
@@ -92,6 +92,8 @@ const Route = ({
appUrl: string;
disabled?: boolean;
}) => {
const { t } = useLocale();
const index = routes.indexOf(route);
const { data: eventTypesByGroup } = trpc.viewer.eventTypes.getByViewer.useQuery({
@@ -128,6 +130,16 @@ const Route = ({
});
});
// /team/{TEAM_SLUG}/{EVENT_SLUG} -> /team/{TEAM_SLUG}
const eventTypePrefix =
eventOptions.length !== 0
? eventOptions[0].value.substring(0, eventOptions[0].value.lastIndexOf("/") + 1)
: "";
const [customEventTypeSlug, setCustomEventTypeSlug] = useState(
!isRouter(route) ? route.action.value.split("/").pop() : ""
);
const onChange = (route: Route, immutableTree: ImmutableTree, config: QueryBuilderUpdatedConfig) => {
const jsonTree = QbUtils.getTree(immutableTree);
setRoute(route.id, {
@@ -199,7 +211,7 @@ const Route = ({
<div>
<div className="text-emphasis flex w-full items-center text-sm">
<div className="flex flex-grow-0 whitespace-nowrap">
<span>Send Booker to</span>
<span>{t("send_booker_to")}</span>
</div>
<Select
isDisabled={disabled}
@@ -257,15 +269,50 @@ const Route = ({
<Select
required
isDisabled={disabled}
options={eventOptions}
options={
eventOptions.length !== 0
? eventOptions.concat({ label: t("Custom"), value: "custom" })
: []
}
onChange={(option) => {
if (!option) {
return;
}
setRoute(route.id, { action: { ...route.action, value: option.value } });
if (option.value !== "custom") {
setRoute(route.id, { action: { ...route.action, value: option.value } });
} else {
setRoute(route.id, { action: { ...route.action, value: "custom" } });
setCustomEventTypeSlug("");
}
}}
value={eventOptions.find((eventOption) => eventOption.value === route.action.value)}
value={
eventOptions.length !== 0 && route.action.value !== ""
? eventOptions.find((eventOption) => eventOption.value === route.action.value) || {
label: t("custom"),
value: "custom",
}
: undefined
}
/>
{eventOptions.length !== 0 &&
route.action.value !== "" &&
!eventOptions.find((eventOption) => eventOption.value === route.action.value) && (
<TextField
disabled={disabled}
className="border-default flex w-full flex-grow text-sm"
containerClassName="w-full mt-2"
addOnLeading={eventTypePrefix}
required
value={customEventTypeSlug}
onChange={(e) => {
setCustomEventTypeSlug(e.target.value);
setRoute(route.id, {
action: { ...route.action, value: `${eventTypePrefix}${e.target.value}` },
});
}}
placeholder="event-url"
/>
)}
</div>
)
) : null}
@@ -20,6 +20,7 @@ import { Button, showToast, useCalcomTheme } from "@calcom/ui";
import FormInputFields from "../../components/FormInputFields";
import getFieldIdentifier from "../../lib/getFieldIdentifier";
import { processRoute } from "../../lib/processRoute";
import { substituteVariables } from "../../lib/substituteVariables";
import transformResponse from "../../lib/transformResponse";
import type { Response, Route } from "../../types/types";
import { getServerSideProps } from "./getServerSideProps";
@@ -101,7 +102,9 @@ function RoutingForm({ form, profile, ...restProps }: Props) {
if (decidedAction.type === "customPageMessage") {
setCustomPageMessage(decidedAction.value);
} else if (decidedAction.type === "eventTypeRedirectUrl") {
await router.push(`/${decidedAction.value}?${allURLSearchParams}`);
const eventTypeUrlWithVariables = substituteVariables(decidedAction.value, response, fields);
await router.push(`/${eventTypeUrlWithVariables}?${allURLSearchParams}`);
} else if (decidedAction.type === "externalRedirectUrl") {
window.parent.location.href = `${decidedAction.value}?${allURLSearchParams}`;
}
@@ -142,7 +145,7 @@ function RoutingForm({ form, profile, ...restProps }: Props) {
<form onSubmit={handleOnSubmit}>
<div className="mb-8">
<h1 className="font-cal text-emphasis mb-1 text-xl font-bold tracking-wide">
<h1 className="font-cal text-emphasis mb-1 text-xl font-bold tracking-wide">
{form.name}
</h1>
{form.description ? (