Skip to content

Commit ee6989a

Browse files
refactor: split raqbUtils into server and client modules to fix RAQB import in server context (calcom#26112)
* refactor: add server and client raqbUtils modules Add two new modules to split RAQB utilities: - raqbUtils.server.ts: Server-safe utilities without RAQB runtime - raqbUtils.client.ts: Client-only utilities requiring RAQB runtime This enables server-side code to import RAQB utilities without pulling in the react-awesome-query-builder runtime library. Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com> * refactor: update imports to use server/client raqbUtils modules and fix lint warnings Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com> * fix: fix type errors in getLuckyUser.ts and remove dead code in RouteBuilder.tsx Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 545eed9 commit ee6989a

7 files changed

Lines changed: 292 additions & 313 deletions

File tree

apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/RouteBuilder.tsx

Lines changed: 16 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import type { ImmutableTree, BuilderProps, Config } from "react-awesome-query-bu
88
import type { JsonTree } from "react-awesome-query-builder";
99
import type { UseFormReturn } from "react-hook-form";
1010
import { Toaster } from "sonner";
11-
import type { z } from "zod";
1211

13-
import { buildEmptyQueryValue, raqbQueryValueUtils } from "@calcom/app-store/_utils/raqb/raqbUtils";
12+
import { buildEmptyQueryValue } from "@calcom/app-store/_utils/raqb/raqbUtils.client";
13+
import { raqbQueryValueUtils } from "@calcom/app-store/_utils/raqb/raqbUtils.server";
1414
import { routingFormAppComponents } from "@calcom/app-store/routing-forms/appComponents";
1515
import DynamicAppComponent from "@calcom/app-store/routing-forms/components/DynamicAppComponent";
1616
import { EmptyState } from "@calcom/app-store/routing-forms/components/_components/EmptyState";
@@ -39,7 +39,6 @@ import type {
3939
EditFormRoute,
4040
AttributeRoutingConfig,
4141
} from "@calcom/app-store/routing-forms/types/types";
42-
import type { zodRoutes } from "@calcom/app-store/routing-forms/zod";
4342
import { RouteActionType } from "@calcom/app-store/routing-forms/zod";
4443
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
4544
import type { EventTypesByViewer } from "@calcom/features/eventtypes/lib/getEventTypesByViewer";
@@ -83,32 +82,36 @@ function useEnsureEventTypeIdInRedirectUrlAction({
8382
eventOptions: { label: string; value: string; eventTypeId: number }[];
8483
setRoute: SetRoute;
8584
}) {
85+
const routeActionValue = isRouter(route) ? undefined : route.action.value;
86+
const routeActionType = isRouter(route) ? undefined : route.action.type;
87+
const routeActionEventTypeId = isRouter(route) ? undefined : route.action.eventTypeId;
88+
8689
useEffect(() => {
8790
if (isRouter(route)) {
8891
return;
8992
}
9093

9194
if (
92-
route.action.type !== RouteActionType.EventTypeRedirectUrl ||
95+
routeActionType !== RouteActionType.EventTypeRedirectUrl ||
9396
// Must not be set already. Could be zero as well for custom
94-
route.action.eventTypeId !== undefined
97+
routeActionEventTypeId !== undefined
9598
) {
9699
return;
97100
}
98101

99-
const matchingOption = eventOptions.find((eventOption) => eventOption.value === route.action.value);
102+
const matchingOption = eventOptions.find((eventOption) => eventOption.value === routeActionValue);
100103
if (!matchingOption) {
101104
return;
102105
}
103106
setRoute(route.id, {
104107
action: { ...route.action, eventTypeId: matchingOption.eventTypeId },
105108
});
106-
}, [eventOptions, setRoute, route.id, (route as unknown as any).action?.value]);
109+
}, [eventOptions, setRoute, route, routeActionValue, routeActionType, routeActionEventTypeId]);
107110
}
108111

109112
const hasRules = (route: EditFormRoute) => {
110113
if (isRouter(route)) return false;
111-
route.queryValue.children1 && Object.keys(route.queryValue.children1).length;
114+
return route.queryValue.children1 && Object.keys(route.queryValue.children1).length;
112115
};
113116

114117
function getEmptyQueryValue() {
@@ -147,14 +150,14 @@ const buildEventsData = ({
147150
label: string;
148151
value: string;
149152
eventTypeId: number;
150-
eventTypeAppMetadata?: Record<string, any>;
153+
eventTypeAppMetadata?: Record<string, unknown>;
151154
isRRWeightsEnabled: boolean;
152155
}[] = [];
153156
const eventTypesMap = new Map<
154157
number,
155158
{
156159
schedulingType: SchedulingType | null;
157-
eventTypeAppMetadata?: Record<string, any>;
160+
eventTypeAppMetadata?: Record<string, unknown>;
158161
}
159162
>();
160163
eventTypesByGroup?.eventTypeGroups.forEach((group) => {
@@ -392,13 +395,11 @@ const Route = ({
392395
? eventOptions[0].value.substring(0, eventOptions[0].value.lastIndexOf("/") + 1)
393396
: "";
394397

395-
const [customEventTypeSlug, setCustomEventTypeSlug] = useState<string>("");
396-
397-
useEffect(() => {
398+
const [customEventTypeSlug, setCustomEventTypeSlug] = useState<string>(() => {
398399
const isCustom =
399400
!isRouter(route) && !eventOptions.find((eventOption) => eventOption.value === route.action.value);
400-
setCustomEventTypeSlug(isCustom && !isRouter(route) ? route.action.value.split("/").pop() ?? "" : "");
401-
}, []);
401+
return isCustom && !isRouter(route) ? route.action.value.split("/").pop() ?? "" : "";
402+
});
402403

403404
useEnsureEventTypeIdInRedirectUrlAction({
404405
route,
@@ -1147,67 +1148,6 @@ const Routes = ({
11471148
hookForm,
11481149
});
11491150

1150-
const { data: allForms } = trpc.viewer.appRoutingForms.forms.useQuery();
1151-
1152-
const notHaveAttributesQuery = ({ form }: { form: { routes: z.infer<typeof zodRoutes> } }) => {
1153-
return form.routes?.every((route) => {
1154-
if (isRouter(route)) {
1155-
return true;
1156-
}
1157-
return !route.attributesQueryValue;
1158-
});
1159-
};
1160-
1161-
const availableRouters =
1162-
allForms?.filtered
1163-
.filter(({ form: router }) => {
1164-
const routerValidInContext = areTheySiblingEntities({
1165-
entity1: {
1166-
teamId: router.teamId ?? null,
1167-
// group doesn't have userId. The query ensures that it belongs to the user only, if teamId isn't set. So, I am manually setting it to the form userId
1168-
userId: router.userId,
1169-
},
1170-
entity2: {
1171-
teamId: hookForm.getValues().teamId ?? null,
1172-
userId: hookForm.getValues().userId,
1173-
},
1174-
});
1175-
return router.id !== hookForm.getValues().id && routerValidInContext;
1176-
})
1177-
// We don't want to support picking forms that have attributes query. We can consider it later.
1178-
// This is mainly because the Router picker feature is pretty much not used and we don't want to complicate things
1179-
.filter(({ form }) => {
1180-
return notHaveAttributesQuery({ form: form });
1181-
})
1182-
.map(({ form: router }) => {
1183-
return {
1184-
value: router.id,
1185-
label: router.name,
1186-
name: router.name,
1187-
description: router.description,
1188-
isDisabled: false,
1189-
};
1190-
}) || [];
1191-
1192-
// const isConnectedForm = (id: string) => form.connectedForms.map((f) => f.id).includes(id);
1193-
1194-
// const routers: any[] = [];
1195-
/* Disable this feature for new forms till we get it fully working with Routing Form with Attributes. This isn't much used feature */
1196-
// const routers = availableRouters.map((r) => {
1197-
// // Reset disabled state
1198-
// r.isDisabled = false;
1199-
1200-
// // Can't select a form as router that is already a connected form. It avoids cyclic dependency
1201-
// if (isConnectedForm(r.value)) {
1202-
// r.isDisabled = true;
1203-
// }
1204-
// // A route that's already used, can't be reselected
1205-
// if (routes.find((route) => route.id === r.value)) {
1206-
// r.isDisabled = true;
1207-
// }
1208-
// return r;
1209-
// });
1210-
12111151
const createRoute = useCreateRoute({
12121152
routes,
12131153
setRoutes,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"use client";
2+
3+
/**
4+
* Client-only RAQB utilities that require the react-awesome-query-builder runtime.
5+
* These functions should only be imported in client-side React components.
6+
*/
7+
import type { JsonTree } from "react-awesome-query-builder";
8+
import type { Config } from "react-awesome-query-builder";
9+
import { Utils as QbUtils } from "react-awesome-query-builder";
10+
11+
export function buildEmptyQueryValue() {
12+
return { id: QbUtils.uuid(), type: "group" as const };
13+
}
14+
15+
export const buildStateFromQueryValue = ({
16+
queryValue,
17+
config,
18+
}: {
19+
/**
20+
* Allow null as the queryValue as initially there could be no queryValue and without that we can't build the state and can't show the UI
21+
*/
22+
queryValue: JsonTree | null;
23+
config: Config;
24+
}) => {
25+
const queryValueToUse = queryValue || buildEmptyQueryValue();
26+
const immutableTree = QbUtils.checkTree(QbUtils.loadTree(queryValueToUse), config);
27+
return {
28+
state: {
29+
tree: immutableTree,
30+
config,
31+
},
32+
queryValue: QbUtils.getTree(immutableTree),
33+
};
34+
};

0 commit comments

Comments
 (0)