import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation"; import { InstallAppButton } from "@calcom/app-store/components"; import { doesAppSupportTeamInstall, isConferencing } from "@calcom/app-store/utils"; import { AppOnboardingSteps } from "@calcom/lib/apps/appOnboardingSteps"; import { getAppOnboardingUrl } from "@calcom/lib/apps/getAppOnboardingUrl"; import classNames from "@calcom/lib/classNames"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { UserAdminTeams } from "@calcom/lib/server/repository/user"; import type { AppFrontendPayload as App } from "@calcom/types/App"; import type { CredentialFrontendPayload as Credential } from "@calcom/types/Credential"; import type { ButtonProps } from "@calcom/ui"; import { Badge, showToast } from "@calcom/ui"; import { Button } from "../button"; interface AppCardProps { app: App; credentials?: Credential[]; searchText?: string; userAdminTeams?: UserAdminTeams; } export function AppCard({ app, credentials, searchText, userAdminTeams }: AppCardProps) { const { t } = useLocale(); const router = useRouter(); const allowedMultipleInstalls = app.categories && app.categories.indexOf("calendar") > -1; const appAdded = (credentials && credentials.length) || 0; const enabledOnTeams = doesAppSupportTeamInstall({ appCategories: app.categories, concurrentMeetings: app.concurrentMeetings, isPaid: !!app.paid, }); const appInstalled = enabledOnTeams && userAdminTeams ? userAdminTeams.length < appAdded : appAdded > 0; const mutation = useAddAppMutation(null, { onSuccess: (data) => { if (data?.setupPending) return; setIsLoading(false); showToast(t("app_successfully_installed"), "success"); }, onError: (error) => { if (error instanceof Error) showToast(error.message || t("app_could_not_be_installed"), "error"); setIsLoading(false); }, }); const [searchTextIndex, setSearchTextIndex] = useState(undefined); /** * @todo Refactor to eliminate the isLoading state by using mutation.isPending directly. * Currently, the isLoading state is used to manage the loading indicator due to the delay in loading the next page, * which is caused by heavy queries in getServersideProps. This causes the loader to turn off before the page changes. */ const [isLoading, setIsLoading] = useState(mutation.isPending); useEffect(() => { setSearchTextIndex(searchText ? app.name.toLowerCase().indexOf(searchText.toLowerCase()) : undefined); }, [app.name, searchText]); const handleAppInstall = () => { setIsLoading(true); if (isConferencing(app.categories)) { mutation.mutate({ type: app.type, variant: app.variant, slug: app.slug, returnTo: WEBAPP_URL + getAppOnboardingUrl({ slug: app.slug, step: AppOnboardingSteps.EVENT_TYPES_STEP, }), }); } else if ( !doesAppSupportTeamInstall({ appCategories: app.categories, concurrentMeetings: app.concurrentMeetings, isPaid: !!app.paid, }) ) { mutation.mutate({ type: app.type }); } else { router.push(getAppOnboardingUrl({ slug: app.slug, step: AppOnboardingSteps.ACCOUNTS_STEP })); } }; return (
{`${app.name}

{searchTextIndex != undefined && searchText ? ( <> {app.name.substring(0, searchTextIndex)} {app.name.substring(searchTextIndex, searchTextIndex + searchText.length)} {app.name.substring(searchTextIndex + searchText.length)} ) : ( app.name )}

{/* TODO: add reviews
{props.rating} stars {props.reviews} reviews
*/}

{app.description}

{app.isGlobal || (credentials && credentials.length > 0 && allowedMultipleInstalls) ? !app.isGlobal && ( !data.installed)} wrapperClassName="[@media(max-width:260px)]:w-full" render={({ useDefaultComponent, ...props }) => { if (useDefaultComponent) { props = { ...props, onClick: () => { handleAppInstall(); }, loading: isLoading, }; } return ; }} /> ) : credentials && !appInstalled && ( !data.installed)} teamsPlanRequired={app.teamsPlanRequired} render={({ useDefaultComponent, ...props }) => { if (useDefaultComponent) { props = { ...props, disabled: !!props.disabled, onClick: () => { handleAppInstall(); }, loading: isLoading, }; } return ; }} /> )}
{appAdded > 0 ? {t("installed", { count: appAdded })} : null} {app.isTemplate && ( Template )} {(app.isDefault || (!app.isDefault && app.isGlobal)) && ( {t("default")} )}
); } const InstallAppButtonChild = ({ paid, ...props }: { paid: App["paid"]; } & ButtonProps) => { const { t } = useLocale(); // Paid apps don't support team installs at the moment // Also, cal.ai(the only paid app at the moment) doesn't support team install either if (paid) { return ( ); } return ( ); };