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:
co-authored by
Joe Au-Yeung
Alex van Andel
parent
d43d8700d0
commit
62fea25eb9
@@ -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 it’s 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 it’s 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"
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user