feat: show top banner for broken integrations [Cal 3125] (#13720)
* added InvalidAppCredentialBanner * Create InvalidAppCredentialsBanner.tsx * Update getUserTopBanners.handler.ts * Create checkForInvalidAppCredentials.ts * Update common.json * Update common.json * Update getUserTopBanners.handler.ts * Update checkForInvalidAppCredentials.ts * also included team's credentials * reverse yarnlock changes * fix .env * fixed types * chore: translation --------- Co-authored-by: Udit Takkar <udit222001@gmail.com>
This commit is contained in:
co-authored by
Udit Takkar
parent
67256b5b6e
commit
aedc88f975
@@ -1601,7 +1601,8 @@
|
||||
"attendee_email_variable": "Attendee email",
|
||||
"attendee_email_info": "The person booking's email",
|
||||
"kbar_search_placeholder": "Type a command or search...",
|
||||
"invalid_credential": "Oh no! Looks like permission expired or was revoked. Please reinstall again.",
|
||||
"invalid_credential": "It looks like permissions expired or were revoked for {{appName}}.",
|
||||
"invalid_credential_action": "Reinstall app",
|
||||
"reschedule_reason": "Reschedule reason",
|
||||
"choose_common_schedule_team_event": "Choose a common schedule",
|
||||
"choose_common_schedule_team_event_description": "Enable this if you want to use a common schedule between hosts. When disabled, each host will be booked based on their default schedule.",
|
||||
|
||||
@@ -29,6 +29,10 @@ import AdminPasswordBanner, {
|
||||
import CalendarCredentialBanner, {
|
||||
type CalendarCredentialBannerProps,
|
||||
} from "@calcom/features/users/components/CalendarCredentialBanner";
|
||||
import {
|
||||
InvalidAppCredentialBanners,
|
||||
type InvalidAppCredentialBannersProps,
|
||||
} from "@calcom/features/users/components/InvalidAppCredentialsBanner";
|
||||
import UserV2OptInBanner from "@calcom/features/users/components/UserV2OptInBanner";
|
||||
import VerifyEmailBanner, {
|
||||
type VerifyEmailBannerProps,
|
||||
@@ -163,6 +167,7 @@ type BannerTypeProps = {
|
||||
adminPasswordBanner: AdminPasswordBannerProps;
|
||||
impersonationBanner: ImpersonatingBannerProps;
|
||||
calendarCredentialBanner: CalendarCredentialBannerProps;
|
||||
invalidAppCredentialBanners: InvalidAppCredentialBannersProps;
|
||||
};
|
||||
|
||||
type BannerType = keyof BannerTypeProps;
|
||||
@@ -178,6 +183,9 @@ const BannerComponent: BannerComponent = {
|
||||
adminPasswordBanner: (props: AdminPasswordBannerProps) => <AdminPasswordBanner {...props} />,
|
||||
impersonationBanner: (props: ImpersonatingBannerProps) => <ImpersonatingBanner {...props} />,
|
||||
calendarCredentialBanner: (props: CalendarCredentialBannerProps) => <CalendarCredentialBanner {...props} />,
|
||||
invalidAppCredentialBanners: (props: InvalidAppCredentialBannersProps) => (
|
||||
<InvalidAppCredentialBanners {...props} />
|
||||
),
|
||||
};
|
||||
|
||||
function useRedirectToOnboardingIfNeeded() {
|
||||
@@ -255,6 +263,7 @@ const Layout = (props: LayoutProps) => {
|
||||
|
||||
{/* todo: only run this if timezone is different */}
|
||||
<TimezoneChangeDialog />
|
||||
|
||||
<div className="flex min-h-screen flex-col">
|
||||
{banners && (
|
||||
<div className="sticky top-0 z-10 w-full divide-y divide-black">
|
||||
@@ -278,6 +287,9 @@ const Layout = (props: LayoutProps) => {
|
||||
} else if (key === "calendarCredentialBanner") {
|
||||
const Banner = BannerComponent[key];
|
||||
return <Banner data={banners[key]} key={key} />;
|
||||
} else if (key === "invalidAppCredentialBanners") {
|
||||
const Banner = BannerComponent[key];
|
||||
return <Banner data={banners[key]} key={key} />;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { type RouterOutputs } from "@calcom/trpc";
|
||||
import { TopBanner } from "@calcom/ui";
|
||||
|
||||
export type InvalidAppCredentialBannersProps = {
|
||||
data: RouterOutputs["viewer"]["getUserTopBanners"]["invalidAppCredentialBanners"];
|
||||
};
|
||||
|
||||
export function InvalidAppCredentialBanners({ data }: InvalidAppCredentialBannersProps) {
|
||||
if (data.length === 0) {
|
||||
return null; // No need to show banner if the array is empty
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{data.map((app) => (
|
||||
<InvalidAppCredentialBanner key={app.slug} name={app.name} slug={app.slug} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export type InvalidAppCredentialBannerProps = {
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export function InvalidAppCredentialBanner({ name, slug }: InvalidAppCredentialBannerProps) {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
|
||||
const handleClick = () => {
|
||||
router.push(`/apps/${slug}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<TopBanner
|
||||
text={` ${t("invalid_credential", { appName: name })} `}
|
||||
variant="warning"
|
||||
actions={
|
||||
<button className="border-b border-b-black" onClick={handleClick}>
|
||||
{t("invalid_credential_action")}
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { getAppFromSlug } from "@calcom/app-store/utils";
|
||||
import { type InvalidAppCredentialBannerProps } from "@calcom/features/users/components/InvalidAppCredentialsBanner";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/client";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
type checkInvalidAppCredentialsOptions = {
|
||||
ctx: {
|
||||
user: NonNullable<TrpcSessionUser>;
|
||||
};
|
||||
};
|
||||
|
||||
export const checkInvalidAppCredentials = async ({ ctx }: checkInvalidAppCredentialsOptions) => {
|
||||
const userId = ctx.user.id;
|
||||
|
||||
const apps = await prisma.credential.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
userId: userId,
|
||||
},
|
||||
{
|
||||
team: {
|
||||
members: {
|
||||
some: {
|
||||
userId: userId,
|
||||
accepted: true,
|
||||
role: { in: [MembershipRole.ADMIN, MembershipRole.OWNER] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
invalid: true,
|
||||
},
|
||||
select: {
|
||||
appId: true,
|
||||
},
|
||||
});
|
||||
|
||||
const appNamesAndSlugs: InvalidAppCredentialBannerProps[] = [];
|
||||
for (const app of apps) {
|
||||
if (app.appId) {
|
||||
const appId = app.appId;
|
||||
const appMeta = await getAppFromSlug(appId);
|
||||
const name = appMeta ? appMeta.name : appId;
|
||||
appNamesAndSlugs.push({ slug: appId, name });
|
||||
}
|
||||
}
|
||||
|
||||
return appNamesAndSlugs;
|
||||
};
|
||||
@@ -5,6 +5,7 @@ import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
import { checkIfOrgNeedsUpgradeHandler } from "../viewer/organizations/checkIfOrgNeedsUpgrade.handler";
|
||||
import { getUpgradeableHandler } from "../viewer/teams/getUpgradeable.handler";
|
||||
import { checkInvalidAppCredentials } from "./checkForInvalidAppCredentials";
|
||||
import { shouldVerifyEmailHandler } from "./shouldVerifyEmail.handler";
|
||||
|
||||
type Props = {
|
||||
@@ -38,14 +39,21 @@ export const getUserTopBannersHandler = async ({ ctx }: Props) => {
|
||||
const upgradeableOrgMememberships = checkIfOrgNeedsUpgradeHandler({ ctx });
|
||||
const shouldEmailVerify = shouldVerifyEmailHandler({ ctx });
|
||||
const isInvalidCalendarCredential = checkInvalidGoogleCalendarCredentials({ ctx });
|
||||
const appsWithInavlidCredentials = checkInvalidAppCredentials({ ctx });
|
||||
|
||||
const [teamUpgradeBanner, orgUpgradeBanner, verifyEmailBanner, calendarCredentialBanner] =
|
||||
await Promise.allSettled([
|
||||
upgradeableTeamMememberships,
|
||||
upgradeableOrgMememberships,
|
||||
shouldEmailVerify,
|
||||
isInvalidCalendarCredential,
|
||||
]);
|
||||
const [
|
||||
teamUpgradeBanner,
|
||||
orgUpgradeBanner,
|
||||
verifyEmailBanner,
|
||||
calendarCredentialBanner,
|
||||
invalidAppCredentialBanners,
|
||||
] = await Promise.allSettled([
|
||||
upgradeableTeamMememberships,
|
||||
upgradeableOrgMememberships,
|
||||
shouldEmailVerify,
|
||||
isInvalidCalendarCredential,
|
||||
appsWithInavlidCredentials,
|
||||
]);
|
||||
|
||||
return {
|
||||
teamUpgradeBanner: teamUpgradeBanner.status === "fulfilled" ? teamUpgradeBanner.value : [],
|
||||
@@ -53,5 +61,7 @@ export const getUserTopBannersHandler = async ({ ctx }: Props) => {
|
||||
verifyEmailBanner: verifyEmailBanner.status === "fulfilled" ? !verifyEmailBanner.value.isVerified : false,
|
||||
calendarCredentialBanner:
|
||||
calendarCredentialBanner.status === "fulfilled" ? calendarCredentialBanner.value : false,
|
||||
invalidAppCredentialBanners:
|
||||
invalidAppCredentialBanners.status === "fulfilled" ? invalidAppCredentialBanners.value : [],
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user