Files
calendar/apps/web/components/apps/CalendarListContainer.tsx
T
Alex van AndelandGitHub 1dd2652080 fix: Duplicate calls syncing Calendar integrations (#26858)
* fix: Duplicate calls syncing Calendar integrations

* fix: TS error in handler

* Minor fixup

* Set cacheUpdatedAt at correct depth of array

* Import RouterOutputs
2026-01-15 00:21:40 -03:00

189 lines
5.9 KiB
TypeScript

"use client";
import { InstallAppButton } from "@calcom/app-store/InstallAppButton";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui/components/button";
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
import { ShellSubHeading } from "@calcom/ui/components/layout";
import { List } from "@calcom/ui/components/list";
import { showToast } from "@calcom/ui/components/toast";
import { revalidateSettingsCalendars } from "@calcom/web/app/cache/path/settings/my-account";
import AppListCard from "@calcom/web/modules/apps/components/AppListCard";
import { SkeletonLoader } from "@calcom/web/modules/apps/components/SkeletonLoader";
import { SelectedCalendarsSettingsWebWrapper } from "@calcom/web/modules/calendars/components/SelectedCalendarsSettingsWebWrapper";
import SubHeadingTitleWithConnections from "@components/integrations/SubHeadingTitleWithConnections";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import { QueryCell } from "@lib/QueryCell";
import { Suspense, useEffect } from "react";
import { DestinationCalendarSettingsWebWrapper } from "./DestinationCalendarSettingsWebWrapper";
type CalendarListContainerProps = {
connectedCalendars: RouterOutputs["viewer"]["calendars"]["connectedCalendars"];
installedCalendars: RouterOutputs["viewer"]["apps"]["integrations"];
heading?: boolean;
fromOnboarding?: boolean;
};
type Props = {
onChanged: () => unknown | Promise<unknown>;
fromOnboarding?: boolean;
destinationCalendarId?: string;
isPending?: boolean;
};
function CalendarList(props: Props): JSX.Element {
const { t } = useLocale();
const query = trpc.viewer.apps.integrations.useQuery({ variant: "calendar", onlyInstalled: false });
return (
<QueryCell
query={query}
success={({ data }) => (
<List>
{data.items.map((item) => (
<AppListCard
title={item.name}
key={item.name}
logo={item.logo}
description={item.description}
shouldHighlight
slug={item.slug}
actions={
<InstallAppButton
type={item.type}
render={(buttonProps) => (
<Button color="secondary" {...buttonProps}>
{t("connect")}
</Button>
)}
onChanged={() => props.onChanged()}
/>
}
/>
))}
</List>
)}
/>
);
}
const AddCalendarButton = (): JSX.Element => {
const { t } = useLocale();
return (
<Button color="secondary" StartIcon="plus" href="/apps/categories/calendar">
{t("add_calendar")}
</Button>
);
};
export const CalendarListContainerSkeletonLoader = (): JSX.Element => {
const { t } = useLocale();
return (
<SettingsHeader
title={t("calendars")}
description={t("calendars_description")}
CTA={<AddCalendarButton />}>
<SkeletonLoader />
</SettingsHeader>
);
};
export function CalendarListContainer({
connectedCalendars: data,
installedCalendars,
heading = true,
fromOnboarding,
}: CalendarListContainerProps): JSX.Element {
const { t } = useLocale();
const { error, setQuery: setError } = useRouterQuery("error");
useEffect(() => {
if (error === "account_already_linked" || error === "no_default_calendar") {
showToast(t(error), "error", { id: error });
setError(undefined);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const utils = trpc.useUtils();
const onChanged = (): void => {
Promise.allSettled([
utils.viewer.apps.integrations.invalidate(
{ variant: "calendar", onlyInstalled: true },
{
exact: true,
}
),
utils.viewer.calendars.connectedCalendars.invalidate(),
revalidateSettingsCalendars(),
]);
};
const mutation = trpc.viewer.calendars.setDestinationCalendar.useMutation({
onSuccess: () => {
utils.viewer.calendars.connectedCalendars.invalidate();
revalidateSettingsCalendars();
},
});
let content = null;
if (!!data.connectedCalendars.length || !!installedCalendars?.items.length) {
let headingContent = null;
if (heading) {
headingContent = (
<>
<DestinationCalendarSettingsWebWrapper connectedCalendars={data} />
<Suspense fallback={<SkeletonLoader />}>
<SelectedCalendarsSettingsWebWrapper
onChanged={onChanged}
fromOnboarding={fromOnboarding}
destinationCalendarId={data.destinationCalendar?.externalId}
isPending={mutation.isPending}
connectedCalendars={data}
/>
</Suspense>
</>
);
}
content = headingContent;
} else if (fromOnboarding) {
content = (
<>
{!!data?.connectedCalendars.length && (
<ShellSubHeading
className="mt-4"
title={<SubHeadingTitleWithConnections title={t("connect_additional_calendar")} />}
/>
)}
<CalendarList onChanged={onChanged} />
</>
);
} else {
content = (
<EmptyScreen
Icon="calendar"
headline={t("no_category_apps", {
category: t("calendar").toLowerCase(),
})}
description={t(`no_category_apps_description_calendar`)}
buttonRaw={
<Button color="secondary" data-testid="connect-calendar-apps" href="/apps/categories/calendar">
{t(`connect_calendar_apps`)}
</Button>
}
/>
);
}
return (
<SettingsHeader
title={t("calendars")}
description={t("calendars_description")}
CTA={<AddCalendarButton />}>
{content}
</SettingsHeader>
);
}