fix/connected calendar list different states (#4821)

* Display error if trpc query failed, catch error on getting calendar and cleanup to don't leak credentials to client

* Add calendar connection broke error

* Edit style on alert and disconnecting button

* Ts errors and added translation

* Type fix

* Key and text change for broken calendar connection

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
alannnc
2022-10-04 14:56:38 +00:00
committed by GitHub
co-authored by Joe Au-Yeung Alex van Andel
parent d43d8700d0
commit 62fea25eb9
3 changed files with 115 additions and 33 deletions
@@ -7,6 +7,7 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import { Alert } from "@calcom/ui/v2";
import Badge from "@calcom/ui/v2/core/Badge";
import EmptyScreen from "@calcom/ui/v2/core/EmptyScreen";
import Meta from "@calcom/ui/v2/core/Meta";
@@ -23,7 +24,7 @@ import { CalendarSwitch } from "@components/v2/settings/CalendarSwitch";
const SkeletonLoader = () => {
return (
<SkeletonContainer>
<div className="mt-6 mb-8 space-y-6 divide-y">
<div className="mt-6 mb-8 space-y-6">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
@@ -89,7 +90,30 @@ const CalendarsView = () => {
<List>
{data.connectedCalendars.map((item) => (
<Fragment key={item.credentialId}>
{item.calendars && (
{item.error && item.error.message && (
<Alert
severity="warning"
key={item.credentialId}
title={t("calendar_connection_fail")}
message={item.error.message}
className="mb-4 mt-4"
actions={
<>
{/* @TODO: add a reconnect button, that calls add api and delete old credential */}
<DisconnectIntegration
credentialId={item.credentialId}
trashIcon
onSuccess={() => query.refetch()}
buttonProps={{
className: "border border-gray-300 py-[2px]",
color: "secondary",
}}
/>
</>
}
/>
)}
{item?.error === undefined && item.calendars && (
<ListItem expanded className="flex-col">
<div className="flex w-full flex-1 items-center space-x-3 pb-5 pl-1 pt-1 rtl:space-x-reverse">
{
@@ -154,6 +178,22 @@ const CalendarsView = () => {
/>
);
}}
error={() => {
return (
<Alert
message={
<Trans i18nKey="fetching_calendars_error">
An error ocurred while fetching your Calendars.
<a className="cursor-pointer underline" onClick={() => query.refetch()}>
try again
</a>
.
</Trans>
}
severity="error"
/>
);
}}
/>
</>
);
@@ -1253,9 +1253,9 @@
"create_first_api_key_description": "API keys allow other apps to communicate with Cal.com",
"back_to_signin": "Back to sign in",
"reset_link_sent": "Reset link sent",
"password_reset_email":"An email is on its way to {{email}} with instructions to reset your password.",
"password_reset_leading":"If you don't receive an email soon, check that the email address you entered is correct, check your spam folder or reach out to support if the issue persists.",
"password_updated":"Password updated!",
"password_reset_email": "An email is on its way to {{email}} with instructions to reset your password.",
"password_reset_leading": "If you don't receive an email soon, check that the email address you entered is correct, check your spam folder or reach out to support if the issue persists.",
"password_updated": "Password updated!",
"pending_payment": "Pending payment",
"confirmation_page_rainbow": "Token gate your event with tokens or NFTs on Ethereum, Polygon, and more.",
"not_on_cal": "Not on Cal.com",
@@ -1272,5 +1272,8 @@
"format": "Format",
"uppercase_for_letters": "Use uppercase for all letters",
"replace_whitespaces_underscores": "Replace whitespaces with underscores",
"ignore_special_characters": "Ignore special characters in your Additonal Input label. Use only letters and numbers"
"ignore_special_characters": "Ignore special characters in your Additional Input label. Use only letters and numbers",
"retry": "Retry",
"fetching_calendars_error": "There was a problem fetching your calendars. Please <1>try again</1> or reach out to customer support.",
"calendar_connection_fail": "Calendar connection failed"
}
+66 -27
View File
@@ -8,6 +8,7 @@ import getApps from "@calcom/app-store/utils";
import { getUid } from "@calcom/lib/CalEventParser";
import logger from "@calcom/lib/logger";
import { performance } from "@calcom/lib/server/perfObserver";
import { App } from "@calcom/types/App";
import type { CalendarEvent, EventBusyDate, NewCalendarEventType } from "@calcom/types/Calendar";
import type { EventResult } from "@calcom/types/EventManager";
@@ -33,47 +34,85 @@ export const getConnectedCalendars = async (
) => {
const connectedCalendars = await Promise.all(
calendarCredentials.map(async (item) => {
const { calendar, integration, credential } = item;
const credentialId = credential.id;
if (!calendar) {
try {
const { calendar, integration, credential } = item;
// Don't leak credentials to the client
const credentialId = credential.id;
if (!calendar) {
return {
integration,
credentialId,
};
}
const cals = await calendar.listCalendars();
const calendars = _(cals)
.map((cal) => ({
...cal,
readOnly: cal.readOnly || false,
primary: cal.primary || null,
isSelected: selectedCalendars.some((selected) => selected.externalId === cal.externalId),
credentialId,
}))
.sortBy(["primary"])
.value();
const primary = calendars.find((item) => item.primary) ?? calendars.find((cal) => cal !== undefined);
if (!primary) {
return {
integration,
credentialId,
error: {
message: "No primary calendar found",
},
};
}
return {
integration,
integration: cleanIntegrationKeys(integration),
credentialId,
primary,
calendars,
};
}
const cals = await calendar.listCalendars();
const calendars = _(cals)
.map((cal) => ({
...cal,
readOnly: cal.readOnly || false,
primary: cal.primary || null,
isSelected: selectedCalendars.some((selected) => selected.externalId === cal.externalId),
credentialId,
}))
.sortBy(["primary"])
.value();
const primary = calendars.find((item) => item.primary) ?? calendars.find((cal) => cal !== undefined);
if (!primary) {
} catch (error) {
let errorMessage = "Could not get connected calendars";
// Here you can expect for specific errors
if (error instanceof Error) {
if (error.message === "invalid_grant") {
errorMessage = "Access token expired or revoked";
}
}
return {
integration,
credentialId,
integration: cleanIntegrationKeys(item.integration),
credentialId: item.credential.id,
error: {
message: "No primary calendar found",
message: errorMessage,
},
};
}
return {
integration,
credentialId,
primary,
calendars,
};
})
);
return connectedCalendars;
};
/**
* Important function to prevent leaking credentials to the client
* @param appIntegration
* @returns App
*/
const cleanIntegrationKeys = (
appIntegration: ReturnType<typeof getCalendarCredentials>[number]["integration"] & {
credentials?: Array<Credential>;
credential: Credential;
}
) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { credentials, credential, ...rest } = appIntegration;
return rest;
};
const CACHING_TIME = 30_000; // 30 seconds
const getCachedResults = async (