Skip to content

Commit 4aa040d

Browse files
CarinaWolliCarinaWollihariombalhara
authored
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>
1 parent 12466fb commit 4aa040d

File tree

4 files changed

+83
-6
lines changed

4 files changed

+83
-6
lines changed

apps/web/public/static/locales/en/common.json

+1
Original file line numberDiff line numberDiff line change
@@ -2242,5 +2242,6 @@
22422242
"redirect_to": "Redirect to",
22432243
"having_trouble_finding_time": "Having trouble finding a time?",
22442244
"show_more": "Show more",
2245+
"send_booker_to": "Send Booker to",
22452246
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
22462247
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import slugify from "@calcom/lib/slugify";
2+
3+
import type { Response, Route, Field } from "../types/types";
4+
import getFieldIdentifier from "./getFieldIdentifier";
5+
6+
export const substituteVariables = (
7+
routeValue: Route["action"]["value"],
8+
response: Response,
9+
fields: Field[]
10+
) => {
11+
const regex = /\{([^\}]+)\}/g;
12+
const variables: string[] = routeValue.match(regex)?.map((match: string) => match.slice(1, -1)) || [];
13+
14+
let eventTypeUrl = routeValue;
15+
16+
variables.forEach((variable) => {
17+
for (const key in response) {
18+
const identifier = getFieldIdentifier(fields.find((field) => field.id === key));
19+
if (identifier.toLowerCase() === variable.toLowerCase()) {
20+
eventTypeUrl = eventTypeUrl.replace(`{${variable}}`, slugify(response[key].value.toString() || ""));
21+
}
22+
}
23+
});
24+
25+
return eventTypeUrl;
26+
};

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

+51-4
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ const Route = ({
9292
appUrl: string;
9393
disabled?: boolean;
9494
}) => {
95+
const { t } = useLocale();
96+
9597
const index = routes.indexOf(route);
9698

9799
const { data: eventTypesByGroup } = trpc.viewer.eventTypes.getByViewer.useQuery({
@@ -128,6 +130,16 @@ const Route = ({
128130
});
129131
});
130132

133+
// /team/{TEAM_SLUG}/{EVENT_SLUG} -> /team/{TEAM_SLUG}
134+
const eventTypePrefix =
135+
eventOptions.length !== 0
136+
? eventOptions[0].value.substring(0, eventOptions[0].value.lastIndexOf("/") + 1)
137+
: "";
138+
139+
const [customEventTypeSlug, setCustomEventTypeSlug] = useState(
140+
!isRouter(route) ? route.action.value.split("/").pop() : ""
141+
);
142+
131143
const onChange = (route: Route, immutableTree: ImmutableTree, config: QueryBuilderUpdatedConfig) => {
132144
const jsonTree = QbUtils.getTree(immutableTree);
133145
setRoute(route.id, {
@@ -199,7 +211,7 @@ const Route = ({
199211
<div>
200212
<div className="text-emphasis flex w-full items-center text-sm">
201213
<div className="flex flex-grow-0 whitespace-nowrap">
202-
<span>Send Booker to</span>
214+
<span>{t("send_booker_to")}</span>
203215
</div>
204216
<Select
205217
isDisabled={disabled}
@@ -257,15 +269,50 @@ const Route = ({
257269
<Select
258270
required
259271
isDisabled={disabled}
260-
options={eventOptions}
272+
options={
273+
eventOptions.length !== 0
274+
? eventOptions.concat({ label: t("Custom"), value: "custom" })
275+
: []
276+
}
261277
onChange={(option) => {
262278
if (!option) {
263279
return;
264280
}
265-
setRoute(route.id, { action: { ...route.action, value: option.value } });
281+
if (option.value !== "custom") {
282+
setRoute(route.id, { action: { ...route.action, value: option.value } });
283+
} else {
284+
setRoute(route.id, { action: { ...route.action, value: "custom" } });
285+
setCustomEventTypeSlug("");
286+
}
266287
}}
267-
value={eventOptions.find((eventOption) => eventOption.value === route.action.value)}
288+
value={
289+
eventOptions.length !== 0 && route.action.value !== ""
290+
? eventOptions.find((eventOption) => eventOption.value === route.action.value) || {
291+
label: t("custom"),
292+
value: "custom",
293+
}
294+
: undefined
295+
}
268296
/>
297+
{eventOptions.length !== 0 &&
298+
route.action.value !== "" &&
299+
!eventOptions.find((eventOption) => eventOption.value === route.action.value) && (
300+
<TextField
301+
disabled={disabled}
302+
className="border-default flex w-full flex-grow text-sm"
303+
containerClassName="w-full mt-2"
304+
addOnLeading={eventTypePrefix}
305+
required
306+
value={customEventTypeSlug}
307+
onChange={(e) => {
308+
setCustomEventTypeSlug(e.target.value);
309+
setRoute(route.id, {
310+
action: { ...route.action, value: `${eventTypePrefix}${e.target.value}` },
311+
});
312+
}}
313+
placeholder="event-url"
314+
/>
315+
)}
269316
</div>
270317
)
271318
) : null}

packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { Button, showToast, useCalcomTheme } from "@calcom/ui";
2020
import FormInputFields from "../../components/FormInputFields";
2121
import getFieldIdentifier from "../../lib/getFieldIdentifier";
2222
import { processRoute } from "../../lib/processRoute";
23+
import { substituteVariables } from "../../lib/substituteVariables";
2324
import transformResponse from "../../lib/transformResponse";
2425
import type { Response, Route } from "../../types/types";
2526
import { getServerSideProps } from "./getServerSideProps";
@@ -101,7 +102,9 @@ function RoutingForm({ form, profile, ...restProps }: Props) {
101102
if (decidedAction.type === "customPageMessage") {
102103
setCustomPageMessage(decidedAction.value);
103104
} else if (decidedAction.type === "eventTypeRedirectUrl") {
104-
await router.push(`/${decidedAction.value}?${allURLSearchParams}`);
105+
const eventTypeUrlWithVariables = substituteVariables(decidedAction.value, response, fields);
106+
107+
await router.push(`/${eventTypeUrlWithVariables}?${allURLSearchParams}`);
105108
} else if (decidedAction.type === "externalRedirectUrl") {
106109
window.parent.location.href = `${decidedAction.value}?${allURLSearchParams}`;
107110
}
@@ -142,7 +145,7 @@ function RoutingForm({ form, profile, ...restProps }: Props) {
142145

143146
<form onSubmit={handleOnSubmit}>
144147
<div className="mb-8">
145-
<h1 className="font-cal text-emphasis mb-1 text-xl font-bold tracking-wide">
148+
<h1 className="font-cal text-emphasis mb-1 text-xl font-bold tracking-wide">
146149
{form.name}
147150
</h1>
148151
{form.description ? (

0 commit comments

Comments
 (0)