Skip to content

Commit 1dd2652

Browse files
authored
fix: Duplicate calls syncing Calendar integrations (calcom#26858)
* fix: Duplicate calls syncing Calendar integrations * fix: TS error in handler * Minor fixup * Set cacheUpdatedAt at correct depth of array * Import RouterOutputs
1 parent a708e08 commit 1dd2652

8 files changed

Lines changed: 163 additions & 132 deletions

File tree

Lines changed: 73 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
11
"use client";
22

3-
import { useEffect, Suspense } from "react";
4-
53
import { InstallAppButton } from "@calcom/app-store/InstallAppButton";
6-
import { DestinationCalendarSettingsWebWrapper } from "./DestinationCalendarSettingsWebWrapper";
7-
import { SelectedCalendarsSettingsWebWrapper } from "@calcom/web/modules/calendars/components/SelectedCalendarsSettingsWebWrapper";
8-
import AppListCard from "@calcom/web/modules/apps/components/AppListCard";
9-
import { SkeletonLoader } from "@calcom/web/modules/apps/components/SkeletonLoader";
104
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
115
import { useLocale } from "@calcom/lib/hooks/useLocale";
12-
import { trpc } from "@calcom/trpc/react";
136
import type { RouterOutputs } from "@calcom/trpc/react";
7+
import { trpc } from "@calcom/trpc/react";
148
import { Button } from "@calcom/ui/components/button";
159
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
1610
import { ShellSubHeading } from "@calcom/ui/components/layout";
1711
import { List } from "@calcom/ui/components/list";
1812
import { showToast } from "@calcom/ui/components/toast";
1913
import { revalidateSettingsCalendars } from "@calcom/web/app/cache/path/settings/my-account";
14+
import AppListCard from "@calcom/web/modules/apps/components/AppListCard";
15+
import { SkeletonLoader } from "@calcom/web/modules/apps/components/SkeletonLoader";
16+
import { SelectedCalendarsSettingsWebWrapper } from "@calcom/web/modules/calendars/components/SelectedCalendarsSettingsWebWrapper";
17+
import SubHeadingTitleWithConnections from "@components/integrations/SubHeadingTitleWithConnections";
18+
import useRouterQuery from "@lib/hooks/useRouterQuery";
2019

2120
import { QueryCell } from "@lib/QueryCell";
22-
import useRouterQuery from "@lib/hooks/useRouterQuery";
21+
import { Suspense, useEffect } from "react";
22+
import { DestinationCalendarSettingsWebWrapper } from "./DestinationCalendarSettingsWebWrapper";
2323

24-
import SubHeadingTitleWithConnections from "@components/integrations/SubHeadingTitleWithConnections";
24+
type CalendarListContainerProps = {
25+
connectedCalendars: RouterOutputs["viewer"]["calendars"]["connectedCalendars"];
26+
installedCalendars: RouterOutputs["viewer"]["apps"]["integrations"];
27+
heading?: boolean;
28+
fromOnboarding?: boolean;
29+
};
2530

2631
type Props = {
2732
onChanged: () => unknown | Promise<unknown>;
@@ -30,7 +35,7 @@ type Props = {
3035
isPending?: boolean;
3136
};
3237

33-
function CalendarList(props: Props) {
38+
function CalendarList(props: Props): JSX.Element {
3439
const { t } = useLocale();
3540
const query = trpc.viewer.apps.integrations.useQuery({ variant: "calendar", onlyInstalled: false });
3641

@@ -66,18 +71,16 @@ function CalendarList(props: Props) {
6671
);
6772
}
6873

69-
const AddCalendarButton = () => {
74+
const AddCalendarButton = (): JSX.Element => {
7075
const { t } = useLocale();
7176
return (
72-
<>
73-
<Button color="secondary" StartIcon="plus" href="/apps/categories/calendar">
74-
{t("add_calendar")}
75-
</Button>
76-
</>
77+
<Button color="secondary" StartIcon="plus" href="/apps/categories/calendar">
78+
{t("add_calendar")}
79+
</Button>
7780
);
7881
};
7982

80-
export const CalendarListContainerSkeletonLoader = () => {
83+
export const CalendarListContainerSkeletonLoader = (): JSX.Element => {
8184
const { t } = useLocale();
8285
return (
8386
<SettingsHeader
@@ -89,19 +92,12 @@ export const CalendarListContainerSkeletonLoader = () => {
8992
);
9093
};
9194

92-
type CalendarListContainerProps = {
93-
connectedCalendars: RouterOutputs["viewer"]["calendars"]["connectedCalendars"];
94-
installedCalendars: RouterOutputs["viewer"]["apps"]["integrations"];
95-
heading?: boolean;
96-
fromOnboarding?: boolean;
97-
};
98-
9995
export function CalendarListContainer({
10096
connectedCalendars: data,
10197
installedCalendars,
10298
heading = true,
10399
fromOnboarding,
104-
}: CalendarListContainerProps) {
100+
}: CalendarListContainerProps): JSX.Element {
105101
const { t } = useLocale();
106102
const { error, setQuery: setError } = useRouterQuery("error");
107103

@@ -113,7 +109,7 @@ export function CalendarListContainer({
113109
// eslint-disable-next-line react-hooks/exhaustive-deps
114110
}, []);
115111
const utils = trpc.useUtils();
116-
const onChanged = () =>
112+
const onChanged = (): void => {
117113
Promise.allSettled([
118114
utils.viewer.apps.integrations.invalidate(
119115
{ variant: "calendar", onlyInstalled: true },
@@ -124,59 +120,69 @@ export function CalendarListContainer({
124120
utils.viewer.calendars.connectedCalendars.invalidate(),
125121
revalidateSettingsCalendars(),
126122
]);
127-
123+
};
128124
const mutation = trpc.viewer.calendars.setDestinationCalendar.useMutation({
129125
onSuccess: () => {
130126
utils.viewer.calendars.connectedCalendars.invalidate();
131127
revalidateSettingsCalendars();
132128
},
133129
});
134130

131+
let content = null;
132+
if (!!data.connectedCalendars.length || !!installedCalendars?.items.length) {
133+
let headingContent = null;
134+
if (heading) {
135+
headingContent = (
136+
<>
137+
<DestinationCalendarSettingsWebWrapper connectedCalendars={data} />
138+
<Suspense fallback={<SkeletonLoader />}>
139+
<SelectedCalendarsSettingsWebWrapper
140+
onChanged={onChanged}
141+
fromOnboarding={fromOnboarding}
142+
destinationCalendarId={data.destinationCalendar?.externalId}
143+
isPending={mutation.isPending}
144+
connectedCalendars={data}
145+
/>
146+
</Suspense>
147+
</>
148+
);
149+
}
150+
content = headingContent;
151+
} else if (fromOnboarding) {
152+
content = (
153+
<>
154+
{!!data?.connectedCalendars.length && (
155+
<ShellSubHeading
156+
className="mt-4"
157+
title={<SubHeadingTitleWithConnections title={t("connect_additional_calendar")} />}
158+
/>
159+
)}
160+
<CalendarList onChanged={onChanged} />
161+
</>
162+
);
163+
} else {
164+
content = (
165+
<EmptyScreen
166+
Icon="calendar"
167+
headline={t("no_category_apps", {
168+
category: t("calendar").toLowerCase(),
169+
})}
170+
description={t(`no_category_apps_description_calendar`)}
171+
buttonRaw={
172+
<Button color="secondary" data-testid="connect-calendar-apps" href="/apps/categories/calendar">
173+
{t(`connect_calendar_apps`)}
174+
</Button>
175+
}
176+
/>
177+
);
178+
}
179+
135180
return (
136181
<SettingsHeader
137182
title={t("calendars")}
138183
description={t("calendars_description")}
139184
CTA={<AddCalendarButton />}>
140-
{!!data.connectedCalendars.length || !!installedCalendars?.items.length ? (
141-
<>
142-
{heading && (
143-
<>
144-
<DestinationCalendarSettingsWebWrapper />
145-
<Suspense fallback={<SkeletonLoader />}>
146-
<SelectedCalendarsSettingsWebWrapper
147-
onChanged={onChanged}
148-
fromOnboarding={fromOnboarding}
149-
destinationCalendarId={data.destinationCalendar?.externalId}
150-
isPending={mutation.isPending}
151-
/>
152-
</Suspense>
153-
</>
154-
)}
155-
</>
156-
) : fromOnboarding ? (
157-
<>
158-
{!!data?.connectedCalendars.length && (
159-
<ShellSubHeading
160-
className="mt-4"
161-
title={<SubHeadingTitleWithConnections title={t("connect_additional_calendar")} />}
162-
/>
163-
)}
164-
<CalendarList onChanged={onChanged} />
165-
</>
166-
) : (
167-
<EmptyScreen
168-
Icon="calendar"
169-
headline={t("no_category_apps", {
170-
category: t("calendar").toLowerCase(),
171-
})}
172-
description={t(`no_category_apps_description_calendar`)}
173-
buttonRaw={
174-
<Button color="secondary" data-testid="connect-calendar-apps" href="/apps/categories/calendar">
175-
{t(`connect_calendar_apps`)}
176-
</Button>
177-
}
178-
/>
179-
)}
185+
{content}
180186
</SettingsHeader>
181187
);
182188
}

apps/web/components/apps/DestinationCalendarSettingsWebWrapper.tsx

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import { useLocale } from "@calcom/lib/hooks/useLocale";
2+
import type { RouterOutputs } from "@calcom/trpc/react";
23
import { trpc } from "@calcom/trpc/react";
34
import {
4-
reminderSchema,
55
type ReminderMinutes,
6+
reminderSchema,
67
} from "@calcom/trpc/server/routers/viewer/calendars/setDestinationReminder.schema";
78
import { showToast } from "@calcom/ui/components/toast";
8-
9-
import { AtomsWrapper } from "../../../../packages/platform/atoms/src/components/atoms-wrapper";
109
import { DestinationCalendarSettings } from "../../../../packages/platform/atoms/destination-calendar/DestinationCalendar";
11-
12-
export const DestinationCalendarSettingsWebWrapper = () => {
10+
import { AtomsWrapper } from "../../../../packages/platform/atoms/src/components/atoms-wrapper";
11+
export const DestinationCalendarSettingsWebWrapper = ({
12+
connectedCalendars,
13+
}: {
14+
connectedCalendars?: RouterOutputs["viewer"]["calendars"]["connectedCalendars"];
15+
}): JSX.Element | null => {
1316
const { t } = useLocale();
14-
const calendars = trpc.viewer.calendars.connectedCalendars.useQuery();
17+
const calendars = trpc.viewer.calendars.connectedCalendars.useQuery(undefined, {
18+
initialData: connectedCalendars,
19+
});
1520
const utils = trpc.useUtils();
1621
const mutation = trpc.viewer.calendars.setDestinationCalendar.useMutation({
1722
onSuccess: () => {
@@ -33,7 +38,7 @@ export const DestinationCalendarSettingsWebWrapper = () => {
3338
return null;
3439
}
3540

36-
const handleReminderChange = (value: ReminderMinutes) => {
41+
const handleReminderChange = (value: ReminderMinutes): void => {
3742
const destCal = calendars.data.destinationCalendar;
3843
if (destCal?.credentialId) {
3944
reminderMutation.mutate({
@@ -47,9 +52,10 @@ export const DestinationCalendarSettingsWebWrapper = () => {
4752
const validatedReminderValue = reminderSchema.safeParse(
4853
calendars.data.destinationCalendar.customCalendarReminder
4954
);
50-
const reminderValue: ReminderMinutes = validatedReminderValue.success
51-
? validatedReminderValue.data
52-
: null;
55+
let reminderValue: ReminderMinutes = null;
56+
if (validatedReminderValue.success) {
57+
reminderValue = validatedReminderValue.data;
58+
}
5359

5460
return (
5561
<AtomsWrapper>

apps/web/modules/calendars/components/SelectedCalendarsSettingsWebWrapper.tsx

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import Link from "next/link";
2-
import React from "react";
3-
4-
import AppListCard from "@calcom/web/modules/apps/components/AppListCard";
5-
import CredentialActionsDropdown from "@calcom/web/modules/apps/components/CredentialActionsDropdown";
1+
import { SelectedCalendarsSettings } from "@calcom/atoms/selected-calendars/SelectedCalendarsSettings";
62
import AdditionalCalendarSelector from "@calcom/features/calendars/AdditionalCalendarSelector";
73
import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch";
84
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -11,8 +7,10 @@ import { trpc } from "@calcom/trpc/react";
117
import { Alert } from "@calcom/ui/components/alert";
128
import { Select } from "@calcom/ui/components/form";
139
import { List } from "@calcom/ui/components/list";
14-
15-
import { SelectedCalendarsSettings } from "@calcom/atoms/selected-calendars/SelectedCalendarsSettings";
10+
import AppListCard from "@calcom/web/modules/apps/components/AppListCard";
11+
import CredentialActionsDropdown from "@calcom/web/modules/apps/components/CredentialActionsDropdown";
12+
import Link from "next/link";
13+
import React from "react";
1614

1715
export enum SelectedCalendarSettingsScope {
1816
User = "user",
@@ -30,6 +28,7 @@ type SelectedCalendarsSettingsWebWrapperProps = {
3028
scope?: SelectedCalendarSettingsScope;
3129
setScope?: (scope: SelectedCalendarSettingsScope) => void;
3230
disableConnectionModification?: boolean;
31+
connectedCalendars?: RouterOutputs["viewer"]["calendars"]["connectedCalendars"];
3332
};
3433

3534
const ConnectedCalendarList = ({
@@ -92,7 +91,12 @@ const ConnectedCalendarList = ({
9291
isChecked={cal.isSelected}
9392
destination={cal.externalId === destinationCalendarId}
9493
credentialId={cal.credentialId}
95-
eventTypeId={shouldUseEventTypeScope ? eventTypeId : null}
94+
eventTypeId={(() => {
95+
if (shouldUseEventTypeScope) {
96+
return eventTypeId;
97+
}
98+
return null;
99+
})()}
96100
delegationCredentialId={connectedCalendar.delegationCredentialId || null}
97101
/>
98102
))}
@@ -145,19 +149,30 @@ export const SelectedCalendarsSettingsWebWrapper = (props: SelectedCalendarsSett
145149
eventTypeId = null,
146150
} = props;
147151

148-
const query = trpc.viewer.calendars.connectedCalendars.useQuery(
149-
{
150-
eventTypeId: scope === SelectedCalendarSettingsScope.EventType ? eventTypeId! : null,
151-
},
152-
{
153-
suspense: true,
154-
refetchOnWindowFocus: false,
155-
}
156-
);
152+
let queryInput: { eventTypeId: number } | undefined;
153+
if (scope === SelectedCalendarSettingsScope.EventType) {
154+
queryInput = { eventTypeId: eventTypeId! };
155+
}
156+
157+
let initialData: RouterOutputs["viewer"]["calendars"]["connectedCalendars"] | undefined;
158+
if (scope === SelectedCalendarSettingsScope.User && props.connectedCalendars) {
159+
initialData = props.connectedCalendars;
160+
}
157161

158-
const { isPending } = props;
162+
const query = trpc.viewer.calendars.connectedCalendars.useQuery(queryInput, {
163+
initialData,
164+
suspense: true,
165+
refetchOnWindowFocus: false,
166+
});
167+
168+
const isPending = props.isPending;
159169
const showScopeSelector = !!props.eventTypeId;
160-
const isDisabled = disabledScope ? disabledScope === scope : false;
170+
171+
let isDisabled = false;
172+
if (disabledScope) {
173+
isDisabled = disabledScope === scope;
174+
}
175+
161176
const shouldDisableConnectionModification = isDisabled || disableConnectionModification;
162177
return (
163178
<div>
@@ -170,7 +185,7 @@ export const SelectedCalendarsSettingsWebWrapper = (props: SelectedCalendarsSett
170185
scope={scope}
171186
shouldDisableConnectionModification={shouldDisableConnectionModification}
172187
/>
173-
{query.data?.connectedCalendars && query.data?.connectedCalendars.length > 0 ? (
188+
{!!(query.data?.connectedCalendars && query.data?.connectedCalendars.length > 0) && (
174189
<ConnectedCalendarList
175190
fromOnboarding={props.fromOnboarding}
176191
scope={scope}
@@ -180,7 +195,7 @@ export const SelectedCalendarsSettingsWebWrapper = (props: SelectedCalendarsSett
180195
items={query.data.connectedCalendars}
181196
isDisabled={isDisabled}
182197
/>
183-
) : null}
198+
)}
184199
</SelectedCalendarsSettings>
185200
</div>
186201
);

0 commit comments

Comments
 (0)