Skip to content

Commit 75b93ce

Browse files
authored
fix: troubleshooter team events + improve race condition (calcom#25704)
* fix query to bring back teams + fix hover state * fix team issues with troubleshooter
1 parent 161ebdb commit 75b93ce

5 files changed

Lines changed: 67 additions & 40 deletions

File tree

packages/features/troubleshooter/components/EventScheduleItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function EventScheduleItem() {
3131
suffixSlot={
3232
schedule && (
3333
<Link href={`/availability/${schedule.id}`} className="inline-flex">
34-
<Badge color="orange" size="sm" className="hidden hover:cursor-pointer group-hover:inline-flex">
34+
<Badge color="orange" size="sm" className="invisible hover:cursor-pointer group-hover:visible">
3535
{t("edit")}
3636
</Badge>
3737
</Link>

packages/features/troubleshooter/components/EventTypeSelect.tsx

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useMemo, useEffect, startTransition } from "react";
2+
import { shallow } from "zustand/shallow";
23

34
import { trpc } from "@calcom/trpc";
45
import { SelectField } from "@calcom/ui/components/form";
@@ -7,67 +8,80 @@ import { getQueryParam } from "../../bookings/Booker/utils/query-param";
78
import { useTroubleshooterStore } from "../store";
89

910
export function EventTypeSelect() {
10-
const { data: eventTypes, isPending } = trpc.viewer.eventTypes.list.useQuery();
11-
const selectedEventType = useTroubleshooterStore((state) => state.event);
12-
const setSelectedEventType = useTroubleshooterStore((state) => state.setEvent);
13-
14-
const selectedEventQueryParam = getQueryParam("eventType");
11+
const { data: eventTypes, isPending } = trpc.viewer.eventTypes.listWithTeam.useQuery();
12+
const { event: selectedEventType, setEvent: setSelectedEventType } = useTroubleshooterStore(
13+
(state) => ({
14+
event: state.event,
15+
setEvent: state.setEvent,
16+
}),
17+
shallow
18+
);
1519

1620
const options = useMemo(() => {
1721
if (!eventTypes) return [];
1822
return eventTypes.map((e) => ({
1923
label: e.title,
20-
value: e.slug,
24+
value: e.id.toString(),
2125
id: e.id,
2226
duration: e.length,
2327
}));
2428
}, [eventTypes]);
2529

30+
// Initialize event type from query param or default to first event
2631
useEffect(() => {
27-
if (!selectedEventType && eventTypes && eventTypes[0] && !selectedEventQueryParam) {
28-
const { id, slug, length } = eventTypes[0];
29-
setSelectedEventType({
30-
id,
31-
slug,
32-
duration: length,
33-
});
34-
}
35-
// eslint-disable-next-line react-hooks/exhaustive-deps
36-
}, [eventTypes]);
32+
if (!eventTypes || eventTypes.length === 0) return;
3733

38-
useEffect(() => {
39-
if (selectedEventQueryParam) {
40-
// ensure that the update is deferred until the Suspense boundary has finished hydrating
34+
const selectedEventIdParam = getQueryParam("eventTypeId");
35+
const eventTypeId = selectedEventIdParam ? parseInt(selectedEventIdParam, 10) : null;
36+
37+
// If we already have a selected event that matches the query param, don't do anything
38+
if (selectedEventType?.id === eventTypeId) return;
39+
40+
// If there's a query param, try to find and set that event
41+
if (eventTypeId && !isNaN(eventTypeId)) {
4142
startTransition(() => {
42-
const foundEventType = eventTypes?.find((et) => et.slug === selectedEventQueryParam);
43+
const foundEventType = eventTypes.find((et) => et.id === eventTypeId);
4344
if (foundEventType) {
44-
const { id, slug, length } = foundEventType;
45-
setSelectedEventType({ id, slug, duration: length });
46-
} else if (eventTypes && eventTypes[0]) {
47-
const { id, slug, length } = eventTypes[0];
4845
setSelectedEventType({
49-
id,
50-
slug,
51-
duration: length,
46+
id: foundEventType.id,
47+
slug: foundEventType.slug,
48+
duration: foundEventType.length,
49+
teamId: foundEventType.team?.id ?? null,
5250
});
51+
return;
5352
}
5453
});
5554
}
56-
}, [eventTypes, selectedEventQueryParam, setSelectedEventType]);
55+
56+
// If no event is selected and no valid query param, default to first event
57+
if (!selectedEventType && !eventTypeId) {
58+
const firstEvent = eventTypes[0];
59+
setSelectedEventType({
60+
id: firstEvent.id,
61+
slug: firstEvent.slug,
62+
duration: firstEvent.length,
63+
teamId: firstEvent.team?.id ?? null,
64+
});
65+
}
66+
}, [eventTypes, selectedEventType, setSelectedEventType]);
5767

5868
return (
5969
<SelectField
6070
label="Event Type"
6171
options={options}
6272
isDisabled={isPending || options.length === 0}
63-
value={options.find((option) => option.value === selectedEventType?.slug) || options[0]}
73+
value={options.find((option) => option.id === selectedEventType?.id) || options[0]}
6474
onChange={(option) => {
6575
if (!option) return;
66-
setSelectedEventType({
67-
slug: option.value,
68-
id: option.id,
69-
duration: option.duration,
70-
});
76+
const foundEventType = eventTypes?.find((et) => et.id === option.id);
77+
if (foundEventType) {
78+
setSelectedEventType({
79+
id: foundEventType.id,
80+
slug: foundEventType.slug,
81+
duration: foundEventType.length,
82+
teamId: foundEventType.team?.id ?? null,
83+
});
84+
}
7185
}}
7286
/>
7387
);

packages/features/troubleshooter/components/LargeCalendar.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,24 @@ export const LargeCalendar = ({ extraDays }: { extraDays: number }) => {
2828
.add(extraDays - 1, "day")
2929
.utc()
3030
.format(),
31+
eventTypeId: event?.id,
3132
withSource: true,
3233
},
3334
{
3435
enabled: !!session?.user?.username,
3536
}
3637
);
3738

39+
const isTeamEvent = !!event?.teamId;
3840
const { data: schedule } = useSchedule({
3941
username: session?.user.username || "",
40-
eventSlug: event?.slug,
42+
// For team events, don't pass eventSlug to avoid slug lookup issues - use eventId instead
43+
eventSlug: isTeamEvent ? null : event?.slug,
4144
eventId: event?.id,
4245
timezone,
4346
month: startDate.format("YYYY-MM"),
4447
orgSlug: session?.user.org?.slug,
48+
isTeamEvent,
4549
});
4650

4751
const endDate = dayjs(startDate)

packages/features/troubleshooter/store.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type EventType = {
1717
id: number;
1818
slug: string;
1919
duration: number;
20+
teamId?: number | null;
2021
};
2122

2223
export type TroubleshooterStore = {
@@ -76,7 +77,7 @@ export const useTroubleshooterStore = create<TroubleshooterStore>((set, get) =>
7677
event: null,
7778
setEvent: (event: EventType) => {
7879
set({ event });
79-
updateQueryParam("eventType", event.slug ?? "");
80+
updateQueryParam("eventTypeId", event.id.toString());
8081
},
8182
month: getQueryParam("month") || getQueryParam("date") || dayjs().format("YYYY-MM"),
8283
setMonth: (month: string | null) => {

packages/trpc/server/routers/viewer/eventTypes/listWithTeam.handler.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,33 @@ type ListWithTeamOptions = {
1111

1212
export const listWithTeamHandler = async ({ ctx }: ListWithTeamOptions) => {
1313
const userId = ctx.user.id;
14-
const query = Prisma.sql`SELECT "public"."EventType"."id", "public"."EventType"."teamId", "public"."EventType"."title", "public"."EventType"."slug", "j1"."name" as "teamName"
14+
const query = Prisma.sql`SELECT "public"."EventType"."id", "public"."EventType"."teamId", "public"."EventType"."title", "public"."EventType"."slug", "public"."EventType"."length", "j1"."name" as "teamName"
1515
FROM "public"."EventType"
1616
LEFT JOIN "public"."Team" AS "j1" ON ("j1"."id") = ("public"."EventType"."teamId")
1717
WHERE "public"."EventType"."userId" = ${userId}
1818
UNION
19-
SELECT "public"."EventType"."id", "public"."EventType"."teamId", "public"."EventType"."title", "public"."EventType"."slug", "j1"."name" as "teamName"
19+
SELECT "public"."EventType"."id", "public"."EventType"."teamId", "public"."EventType"."title", "public"."EventType"."slug", "public"."EventType"."length", "j1"."name" as "teamName"
2020
FROM "public"."EventType"
2121
INNER JOIN "public"."Team" AS "j1" ON ("j1"."id") = ("public"."EventType"."teamId")
2222
INNER JOIN "public"."Membership" AS "t2" ON "t2"."teamId" = "j1"."id"
2323
WHERE "t2"."userId" = ${userId} AND "t2"."accepted" = true`;
2424

2525
const result = await db.$queryRaw<
26-
{ id: number; teamId: number | null; title: string; slug: string; teamName: string | null }[]
26+
{
27+
id: number;
28+
teamId: number | null;
29+
title: string;
30+
slug: string;
31+
length: number;
32+
teamName: string | null;
33+
}[]
2734
>(query);
2835

2936
return result.map((row) => ({
3037
id: row.id,
3138
team: row.teamId ? { id: row.teamId, name: row.teamName || "" } : null,
3239
title: row.title,
3340
slug: row.slug,
41+
length: row.length,
3442
}));
3543
};

0 commit comments

Comments
 (0)